跳转至

优先队列

优先队列

实际上是一个STL。可以实现堆的功能。默认是大根堆(c++如此,其他语言可能有所不同)

例题 #1 指针堆 最小函数值

\(n\) 个函数,分别为 \(F_1,F_2,\dots,F_n\)。定义 \(F_i(x)=A_ix^2+B_ix+C_i(x\in\mathbb N*)\)。给定这些 \(A_i\)\(B_i\)\(C_i\),请求出所有函数的所有函数值中最小的 \(m\) 个(如有重复的要输出多个)。

输入格式

第一行输入两个正整数 \(n\)\(m\)

以下 \(n\) 行每行三个**正整数**,其中第 \(i\) 行的三个数分别为 \(A_i\)\(B_i\)\(C_i\)

输出格式

输出将这 \(n\) 个函数所有可以生成的函数值排序后的前 \(m\) 个元素。这 \(m\) 个数应该输出到一行,用空格隔开。


很担心的将指针丢入优先队列的题目。考虑每个函数就是一个无限长的队列,队列里的元素是单调递增的。我们使用优先队列维护所有队列的头元素,每次取出最小的,弹出,然后将后面应该元素推入。

/*  Erica N  */
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first
#define itn int

#define rd read()
int read(){
    int xx = 0, ff = 1;char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')ff = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9')xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}

#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }


const int N = 3e5 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;

int a[N],b[N],c[N];
int pre[N];

int getVal(int x){
    return a[x]*pre[x]*pre[x]+b[x]*pre[x]+c[x];
}


priority_queue<pii> pq;


void solve(){
    int n=rd;
    itn m=rd;
    for(int i=1;i<=n;i++){
        a[i]=rd;
        b[i]=rd;
        c[i]=rd;
        pre[i]=max(1ll,-2*a[i]/b[i]);
        pq.push(mp(-getVal(i),i));
    }


    while(m--){
        int x=pq.top().ps;
        cout<<getVal(x)<<' ';
        pq.pop();
        pre[x]++;
        pq.push(mp(-getVal(x),x));
    }
}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);

    int T=1;
    while(T--){
        solve();
    }
    return 0;
}

多个队列替代优先队列/set

特殊场合下多个队列替代优先队列,降低时间复杂度。

例题 #1 [NOIP2004 提高组] 合并果子 加强版

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 \((n - 1)\) 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 \(1\),并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 \(3\) 堆果子,数目依次为 \(1,~2,~9\)。可以先将 \(1\)\(2\) 堆合并,新堆数目为 \(3\),耗费体力为 \(3\)。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 \(12\),耗费体力为 \(12\)。所以多多总共耗费体力为 \(3+12=15\)。可以证明 \(15\) 为最小的体力耗费值。

输入格式

输入的第一行是一个整数 \(n\),代表果子的堆数。 输入的第二行有 \(n\) 个用空格隔开的整数,第 \(i\) 个整数代表第 \(i\) 堆果子的个数 \(a_i\)

输出格式

输出一行一个整数,表示最小耗费的体力值。

  • Subtask 4(40 points):\(1 \leq n \leq 10^7\)

对于全部的测试点,保证 \(1 \leq a_i \leq 10^5\)


原来需要优先队列的做法是带log的,在这里会被卡掉。我们考虑原做法:每次取出最小的两堆,合并。那么我们发现我们合并后的堆的大小是单调递增的,于是我们就可以维护序列p,q,一开始桶排a后加入p,每次比较p,q的top,和p的前两个top,取最小值,弹出后加入q的队尾。

/*
Code by Ntsc_Hodaka
*/

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define pii pair<int,int>

///----///
#define rd read()
inline int read() {
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
inline void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}

///----///
const int N = 1e7 + 5;
const int M = 1e5 + 5;
const int INF = 1e9 + 5;
const int MOD = 1e9+7;
const double eps=1e-7;
int n,a[N],ans,cnt[N];

queue<int> q1,q2;

int qfront(){
    int res;
    if(q2.empty()||(q1.size()&&q1.front()<q2.front())){res=q1.front();q1.pop();}
    else {res=q2.front();q2.pop();}
    return res;

}
signed main(){
    n=rd;
    for(int i=1;i<=n;i++){
        a[i]=rd;
        cnt[a[i]]++;    
    }
    for(int i=1;i<=M;i++){
        while(cnt[i]--){//桶排
            q1.push(i);
            // cout<<i<<' ';
        }
    }

    for(int i=1;i<n;i++){
        int a=qfront(),b=qfront();
        ans+=a+b;
        q2.push(a+b);

    }
    cout<<ans;
    return 0;
}

练习 #1 [NOIP2016 提高组] 蚯蚓

/*
CB Ntsc
*/

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair

#define rd read()
inline int read() {
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
inline void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}



const int N = 1e7 + 5;
const int M = 40;
const int INF = 1e9 + 5;
const int MOD = 1e9 + 7;

int n,m,q,u,v,t,top,sum;
priority_queue<int> ans;
int cn1[N],cn2[N],a[N],h1=1,h2=1,h=1,t1,t2;

bool cmp(int x,int y){
    return x>y;
}
signed main(){
    n=rd,m=rd,q=rd,u=rd,v=rd,t=rd;
    double p=1.00*u/v;
    for(int i=1;i<=n;i++){
        a[i]=rd;
    }

    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=m;i++){
        if(h>n){
            if(cn1[h1]>cn2[h2])top=cn1[h1++];
            else top=cn2[h2++];
        }
        else if(a[h]>=cn1[h1]&&a[h]>=cn2[h2])top=a[h++];
        else if(cn1[h1]>=cn2[h2]&&a[h]<=cn1[h1])top=cn1[h1++];
        else top=cn2[h2++];

        top+=sum;

        int a1=floor(1.00*p*top),a2=top-a1;

        sum+=q;
        a1-=sum;a2-=sum;

        cn1[++t1]=a1,cn2[++t2]=a2;

        if(!(i%t))cout<<top<<' ';
    }

    cout<<endl;
    for(int i=h;i<=n;i++)ans.push(a[i]);
    for(int i=h1;i<=t1;i++)ans.push(cn1[i]);
    for(int i=h2;i<=t2;i++)ans.push(cn2[i]);
    int i=1;
    while(ans.size()){
        if(!(i%t))cout<<ans.top()+sum<<' ';
        ans.pop();
        i++;
    }
    return 0;

}
/*
1
2 5 1 
0 0 1 
0 0 4 

*/

练习 #2 [CSP-S2020] 贪吃蛇

首先肯定是优化模拟,因为每一轮要么蛇数量-1,要么exit。

考虑限制:每条蛇希望在自己不被吃的前提下在决斗中尽可能多吃别的蛇

怎么样判断一个时刻也不应该吃呢?

设当前蛇的集合的最大值为mx,最小值为mn,那么吃了之后变为mx-mn。如果此时mx-mn不是最小的,那么一定选择吃。

证明:设集合从小到大为\(a,b,\dots,c,d\),那么模拟一次,变为了\(b,\dots,d-a,c\),接下来是\(\dots,c-b,d-a\)。我们发现一直有蛇在d前面当垫背。为什么c-b<d-a呢?基本的排序不等式!

如果此时mx-mn是最小的,下一个最大值也不一定会吃它。此时我们就考虑下一个最大值会不会吃它,如果不会那么久mx就可以吃mn,否则游戏结束。

假设当前集合的最大值,次大值,……分别为a,b,c,\dots。我们考虑,如果a吃,那么意味着b不吃。也就是说这种情况最多只能再进行一次吃的操作,游戏必然结束。

那么到底是吃不吃?

如果a吃,那么b必然不吃。b不吃是因为如果b吃,那么c就会吃b,此时b一定不吃。那么c吃意味着d不吃……

我们发现吃和不吃是轮换的。知道最后,只有两条蛇时,一定吃。我们看作一个递归,那么如果集合大小为奇数,那么必然不能吃,否则就吃。

到此,思路就完结了。因为我们要维护max和min,所以我们考虑set。

/*  Erica N  */
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first
#define itn int

#define rd read()
int read(){
    int xx = 0, ff = 1;char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')ff = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9')xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}

#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }


const int N = 3e5 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;

int a[N];
int n;
set<pii> st;

void solve(){
    // cdbg(st.size());
    for(int i=1;i<=n;i++){
        st.insert(mp(a[i],i));
    }

    int ans=0;
    int f=0;//标记阶段2

    while(1){
        // cdbg("OK");
        if(st.size()==2){
            st.erase(st.begin());
            if(f){
                if(1&(f-st.size()))ans=f+1;
                else ans=f;
            }else{
                ans=1;
            }
            break;

        }

        auto mx=st.end();
        mx--;
        int vmx=(*mx).pf;
        int mxid=(*mx).ps;
        st.erase(mx);
        auto mn=st.begin();
        int vmn=(*mn).pf;
        st.erase(mn);
        auto mn2=st.begin();
        // st.erase(mn2);


        int mx2=vmx-vmn;
        st.insert(mp(mx2,mxid));//注意相等时判id
        if(mxid!=(*st.begin()).ps){
            if(f){
                if(1&(f-st.size()))ans=f+1;
                else ans=f;
                break;
            }

          // #2,意思是把上面的insert放到这里。
        }else{
            // #1
            if(!f)f=st.size();//进入阶段2
        }
    }
    cout<<ans<<endl;
    st.clear();
}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);

    int T=rd;
    int TT=0;
    while(T--){
        if(!TT){
             n=rd;
            for(int i=1;i<=n;i++){
                a[i]=rd;
            }
        }else{
            int K=rd;
            for(int i=1;i<=K;i++){
                // cdbg("cg");
                int x=rd,y=rd;
                a[x]=y;
            }
        }
        solve();
        TT++;
    }
    return 0;
}

但是发现70pts高分,过不去(\(\sum n=Tn=O(10^7)\))那么我们就拿出我们的队列替代。

怎么替代?

我们发现我们的操作只有:删除最大值,删除最小值,插入最小值(#1),插入任意值(#2)。

前两个用一个队列即可。插入值?我们考虑性质:#2 插入的值是递减的。那么就很好写了。我们用两个队列维护,打包一下更清楚。

/*  Erica N  */
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first
#define itn int

#define rd read()
int read(){
    int xx = 0, ff = 1;char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')ff = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9')xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}

#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }


const int N = 1e6 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;

int a[N];
int n;



namespace douque{
    //两个单调递增的队列
    list<pii> q,q2;
    pii queryMin(bool del=0){
        pii res;
        if(!q2.size()){
            res=q.front();
            if(del)q.pop_front();
        }
        else if(!q.size()){
            res=q2.front();
            if(del)q2.pop_front();
        }
        else if(q.front().pf<q2.front().pf||(q.front().pf==q2.front().pf&&q.front().ps<q2.front().ps)){
            res=q.front();
            if(del)q.pop_front();
        }else{
            res=q2.front();
            if(del)q2.pop_front();
        }
        return res;
    }

    pii queryMax(bool del=0){
        pii res;
        if(!q2.size()){
            res=q.back();
            if(del)q.pop_back();
        }
        else if(!q.size()){
            res=q2.back();
            if(del)q2.pop_back();
        }
        else if(q.back().pf>q2.back().pf||(q.back().pf==q2.back().pf&&q.back().ps>q2.back().ps)){
            res=q.back();
            if(del)q.pop_back();
        }else{
            res=q2.back();
            if(del)q2.pop_back();
        }
        return res;

    }

    int getsz(){
        return q.size()+q2.size();
    }
}using namespace douque;

void solve(){
    for(int i=1;i<=n;i++){
        q.push_back(mp(a[i],i));
    }



    int ans=0;
    int f=0;//标记阶段2

    while(1){
        if(getsz()==2){
            queryMin(1);
            if(f){
                if(1&(f-1))ans=f+1;
                else ans=f;
            }else{
                ans=1;
            }
            break;

        }

        auto mx=queryMax(1);
        int vmx=(mx).pf;
        int mxid=(mx).ps;
        auto mn=queryMin(1);
        int vmn=(mn).pf;
        // auto mn2=queryMin();

        int mx2=vmx-vmn;
        if(!(mx2<queryMin().pf||(mx2==queryMin().pf&&mxid<queryMin().ps)) ){
            if(f){
                if(1&(f-getsz()-1))ans=f+1;
                else ans=f;
                break;
            }
            q2.push_front(mp(mx2,mxid));
        }else{
            q.push_front(mp(mx2,mxid));
            if(!f)f=getsz();//进入阶段2
        }
    }
    cout<<ans<<endl;
    while(q.size())q.pop_back();
    while(q2.size())q2.pop_back();
}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);

    int T=rd;
    int TT=0;
    while(T--){
        if(!TT){
             n=rd;
            for(int i=1;i<=n;i++){
                a[i]=rd;
            }
        }else{
            int K=rd;
            for(int i=1;i<=K;i++){
                // cdbg("cg");
                int x=rd,y=rd;
                a[x]=y;
            }
        }
        solve();
        TT++;
    }
    return 0;
}