NOIP 2016 蚯蚓

题目链接https://www.luogu.com.cn/problem/P2827

不知道为啥出题人跟蚯蚓过不去

分析

这道题我看到后第一感觉是模拟,因为它的输出很有特点,既有过程也有最终答案,所以不可能会有什么公式之类的,那怎么模拟呢?这个蚯蚓长度是动态更新的,所以我们可以考虑动态维护它的大小,用一个优先队列就行,当然,最开始我觉得能过,因为某OJ上边写的是十秒,(其实后来算了算十秒也是过不了的),好吧你不信的话现在来算一算,二叉堆的时间复杂度是\(O(NlogN)\),放到这题就是\(MlogN\),最大值是多少呢?\(7*10^6*log_210^5\)\(log_210^5\)大概是17左右,7*17是119,总的时间复杂度最低也是\(1.19*10^8\),然而因为\(STL\)自带大常数,所以最多跑到\(10^7\)就会T,故\(10s\)也不够用,那手写堆呢?感觉应该也不行,因为上述只是最低时间复杂度,事实上因为还有输入输出啥的,尤其这题输入输出比较多,也会T掉。

我们先不考虑T掉的问题,这个可以优化,先想想暴力怎么做。其实也很简单,就每次从堆里揪出一只蚯蚓来劈成两半然后再加进去就行,那蚯蚓的自然生长怎么办??也很简单,只有这一只蚯蚓不需要生长,所以我定义一个\(sum\)变量记录蚯蚓的生长,每次让它加上生长长度,把蚯蚓劈完后减去生长长度再扔进去就行,这样就等价于它没有生长。代码如下TLE

#include<cstdio>
#include<queue>
#include<cmath>
using namespace std;
priority_queue<int > que;
int main(){
    int n,m,q,u,v,t;
    int sum=0;
    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
    for(int i=1;i<=n;i++){
        int a;
        scanf("%d",&a);
        que.push(a);
    }
    double p=(double)u/v;
    for(int i=1;i<=m;i++){
        if(i%t==0)
            printf("%d ",que.top()+sum);
        u=que.top()+sum;que.pop();
        int now=(double)u*p;que.push(now-q-sum);que.push(u-now-sum-q);
        sum+=q;
    }
    printf("\n");
    int cnt=0;
    while(!que.empty()){
        cnt++;
        if(cnt%t==0)printf("%d ",que.top()+sum);
        que.pop();
    }
}

优化

优化说起来也很简单,就是不太好想到。对于这种需要重复排序的问题,很可能存在某种单调性,可以使\(O(logN)\)的维护最大值变成\(O(1)\)的,这道题就是。

存在什么单调性呢?假设最开始的每只蚯蚓排序后从大到小依次长\(x_i\),那么我根据题意肯定是从\(x_1\)开始切,切完之后,假设\(\lfloor px\rfloor\)\(l\),另一半为\(r\),那么一定会从\(l_1\)开始单调递减,\(r\)同理,也就是说如果我建三个队列来分别维护初始,\(l\)\(r\),这三个队列都是单调的,即每次查询比较队首即可。下面来证明一下:

先证明\(l\)是单调的,假设\(i<j\),设\(c\)表示蚯蚓增加的长度,根据初始的单调性,\(x_i>=x_j\),所以\(\lfloor px_i\rfloor>=\lfloor px_j\rfloor\)

继而有\(\lfloor px_i+c\rfloor>=\lfloor p(x_j+c)\rfloor\)因为\(p\)小于1,所以\(c>pc\)

因为在下取整号里边的是可以拿出来的,这一点是显然的,拿出来不会改变不等号方向。所以\(\lfloor px_i\rfloor+c>=\lfloor p(x_j+c)\rfloor\)

易得\(\lfloor px_i\rfloor>=\lfloor p(x_j+c)\rfloor-c\),这时你会发现不等号左边就是\(l_i\)右边是\(l_j\),故原式得证。
接下来证明\(r\)是单调的,其实两者差不多,都用到了放缩的思想。

因为\(x_i>=x_j\)所以\((1-p)x_i>=(1-p)x_j即x_i-px_i>=x_j-px_j\)
得,\(\lceil x_i-px_i\rceil>=\lceil x_j-px_j\rceil\)注意这里是上取整

上取整号拆开需要稍微作一下变化,即\(x_i-\lfloor px_i\rfloor>=x_j-\lfloor px_j\rfloor\)

然后可以得到,\(x_i-\lfloor px_i\rfloor>=x_j+c-\lfloor px_j\rfloor-c\)

到这里会发现这个式子很接近于\(r\)了,只差最后一步放缩,因为减去一个正数不会让一个数更大,所以我在右边减去一个数不会让不等号方向改变
于是可得\(x_i-\lfloor px_i\rfloor>=x_j+c-\lfloor p(x_j-c)\rfloor-c\)

故有\(r_i>=r_j\),证毕。

细节

1.longlong用不用

肯定是不需要的,假设最开始都是最长\(10^8\),每秒长\(200\),最多也就长\(7*10^6*200\)\(14*10^8\),二者相加是\(15*10^8\)
\(int\)\(21*10^8\),所以不会爆。

2.最大值的取法

因为上边说了,最大可到\(15*10^8\),所以初始化INF不能用\(0x3f3f3f3f\),把3换成7就行。

3.还跟longlong有关系

那为啥有的代码没用longlong就错,用了就对呢?主要是一个地方

这俩相乘的时候可能会爆longlong,当然你预处理那个分数也就自行忽视。

#include<cstdio>
#include<algorithm>
const int N=7e6+10,INF=0x7f7f7f7f;
using namespace std;
int q1[N],tt1,hh1,q2[N],tt2=-1,hh2,q3[N],tt3=-1,hh3;
int sum;
bool cmp(int a,int b){
    return a>b;
}
int find(){
    int x=-INF;
    if(hh1<=tt1)x=max(x,q1[hh1]);
    if(hh2<=tt2)x=max(x,q2[hh2]);
    if(hh3<=tt3)x=max(x,q3[hh3]);
    if(hh1<=tt1&&x==q1[hh1])hh1++;
    else if(hh2<=tt2&&x==q2[hh2])hh2++;
    else if(hh3<=tt3&&x==q3[hh3])hh3++;
    return x;
}
int main(){
    int n,m,q,u,v,t;
    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
    for(int i=0;i<n;i++)
        scanf("%d",&q1[i]);
    tt1=n-1;
    sort(q1,q1+n,cmp);
    for(int i=1;i<=m;i++){
        int x=find();
        x+=sum;
        int l=x*1ll*u/v;
        int r=x-l;
        sum+=q;
        q2[++tt2]=l-sum;
        q3[++tt3]=r-sum;
        if(i%t==0)printf("%d ",x);
    }
    printf("\n");
    for(int i=1;i<=n+m;i++){
        int x=find();
        if(i%t==0)printf("%d ",x+sum);
    }
    return 0;
}
posted @ 2020-04-16 22:42  An_Fly  阅读(145)  评论(0编辑  收藏  举报