蚯蚓

蚯蚓

给出一个大小为n集合\(\{a_i\}\),每次操作选择集合中最大的元素\(a_i\),将其从集合中删除,然后集合中所有的元素值加上q,再向集合中加入元素\([pa_i]\)\(a_i-[pa_i]\),显然p,q已经给出,给出m,t,询问第\(t,2t,...,[m/t]\)次操作前中集合中最大的元素,并且输出所有操作完以后集合中第\(t,2t,...,[(n+m)/t]\)大的元素。

\(1≤n≤10^5,0≤ai≤10^8,0<p<1,0≤q≤200\)
\(0≤m≤7\times 10^6,1≤t≤71\)

法一:优先队列

注意到集合中每次要选出最大的元素,可以考虑优先队列维护,弹出队首\(a_i\),然后将其变成元素\([pa_i]\)\(a_i-[pa_i]\)加入优先队列,按照题目条件每次输出队首即可。

至于集合中的每个元素都增加一个q,全局中开一个变量保存增加的值(不能理解没关系,具体实现看代码)。

而最后的时候不停地弹出队首,这是第几次弹出队首,队首就是第几大元素,按照指定的要求输出第k大的元素。

最终时间复杂度\((n+m)log(n+m)\),超时。

参考代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <functional>
#define il inline
#define ri register
#define lb double
using namespace std;
priority_queue<int,vector<int>,less<int> >Q;
void pen(int);
il void read(int&);
int main(){
    int n,m,q,u,v,t;lb p;
    read(n),read(m),read(q),
        read(u),read(v),read(t);p=(lb)u/v;
    ri int i,j,gzy(0);for(i=1;i<=n;++i)read(j),Q.push(j);
    for(i=1;i<=m;++i){j=Q.top()+gzy,Q.pop();
        if(!(i%t))pen(j),putchar(' ');gzy+=q;
        Q.push((int)(j*p)-gzy),Q.push(j-(int)(j*p)-gzy);
    }putchar('\n');
    for(i=1;i<=n+m;++i){
        if(!(i%t))pen(Q.top()+gzy),putchar(' ');Q.pop();
    }
    return 0;
}
void pen(int x){
    if(x>9)pen(x/10);putchar(x%10+48);
}
il void read(int &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

法二:单调性+归并排序

一个很隐晦的性质,也许猜问题具有单调性会得到,某次操作下,设集合中两个元素\(a_i\geq a_j\),此时\(a_i\)被选出用于操作变为元素\(pa_i,(1-p)a_i\),经过\(t'\)次操作后,如果这两个元素没有被操作,将变为\(pa_i+qt',(1-p)a_i+qt'\),如果恰好\(j\)被操作了,那么就会分成两个元素\(p(a_j+t'q)=pa_j+pt'q,(1-p)(a_j+t'q)=(1-p)a_j+(1-p)tq'\),因为\(a_i\geq a_j,p\in(0,1),1-p\in(0,1)\),得到

\[p(a_j+t'q)\leq pa_i+qt' \]

\[(1-p)(a_j+t'q)\leq (1-p)a_i+qt' \]

于是对于操作分出的元素,按照其在操作前的元素大小,本身具有单调性,如果没理解这句话,继续后看。

于是我们维护3个队列,第1个队列存储初始的集合,但是要从大到小排序,第二个队列存储每次操作(假设操作的是\(a_i\))产生\(pa_i\),而第三个队列存储\((1-p)a_i\),在当前操作的集合中,后面的被操作的元素,肯定小于等于当前被操作的元素,根据性质所分出来的两个元素乘以\(p\)的会单调递减,乘以\(1-p\)会单调递减,于是只要分别把其入应该入的队列,就可以保证这个队列的单调性,于是三个队列都能保证其单调递减,因此只要取各自的队首就可以知道当前操作下集合中最大的元素。

对于最后的询问,因为三个队列都是有序的,显然多个有序的数列排序,考虑归并,最终可以做到时间复杂度\(O(n+m)\)

参考代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <functional>
#define il inline
#define ri register
#define Size 7200000
using namespace std;
int T[3][Size],L[3],R[3],
    te[Size],tt;
il void read(int&);
int main(){int n,m,q,u,v,t;ri int gzy(0);
    ri double p;read(n),read(m),read(q);
    read(u),read(v),read(t),p=(double)u/v;
    for(int i(1);i<=n;++i)read(T[0][i]);
    sort(T[0]+1,T[0]+n+1,greater<int>());
    L[0]=1,R[0]=n,L[1]=L[2]=1;
    for(ri int i(1),j,k;i<=m;++i){j^=j;
        if(T[1][L[1]]>T[j][L[j]]||L[0]>R[0])j=1;
        if(T[2][L[2]]>T[j][L[j]])j=2;
        k=T[j][L[j]++]+gzy,gzy+=q;
        T[1][++R[1]]=(int)(k*p)-gzy;
        T[2][++R[2]]=k-(int)(k*p)-gzy;
        if(!(i%t))printf("%d ",k);
    }putchar('\n');
    while(L[0]<=R[0]&&L[1]<=R[1])
        if(T[0][L[0]]>T[1][L[1]])te[++tt]=T[0][L[0]++];
        else te[++tt]=T[1][L[1]++];
    while(L[0]<=R[0])te[++tt]=T[0][L[0]++];
    while(L[1]<=R[1])te[++tt]=T[1][L[1]++];
    L[0]=1,R[0]=tt,R[1]=0;
    while(L[0]<=R[0]&&L[2]<=R[2])
        if(te[L[0]]>T[2][L[2]])T[1][++R[1]]=te[L[0]++];
        else T[1][++R[1]]=T[2][L[2]++];
    while(L[0]<=R[0])T[1][++R[1]]=te[L[0]++];
    while(L[2]<=R[2])T[1][++R[1]]=T[2][L[2]++];
    for(ri int i(1);i<=R[1];++i)
        if(!(i%t))printf("%d ",T[1][i]+gzy);
    return 0;
}
il void read(int &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

posted @ 2019-07-24 10:47  a1b3c7d9  阅读(129)  评论(0编辑  收藏  举报