题目链接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("
");
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 floor)为(l),另一半为(r),那么一定会从(l_1)开始单调递减,(r)同理,也就是说如果我建三个队列来分别维护初始,(l),(r),这三个队列都是单调的,即每次查询比较队首即可。下面来证明一下:
先证明(l)是单调的,假设(i<j),设(c)表示蚯蚓增加的长度,根据初始的单调性,(x_i>=x_j),所以(lfloor px_i floor>=lfloor px_j floor)
继而有(lfloor px_i+c floor>=lfloor p(x_j+c) floor)因为(p)小于1,所以(c>pc)。
因为在下取整号里边的是可以拿出来的,这一点是显然的,拿出来不会改变不等号方向。所以(lfloor px_i floor+c>=lfloor p(x_j+c) floor)
易得(lfloor px_i
floor>=lfloor p(x_j+c)
floor-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
ceil>=lceil x_j-px_j
ceil)注意这里是上取整
上取整号拆开需要稍微作一下变化,即(x_i-lfloor px_i floor>=x_j-lfloor px_j floor)
然后可以得到,(x_i-lfloor px_i floor>=x_j+c-lfloor px_j floor-c)
到这里会发现这个式子很接近于(r)了,只差最后一步放缩,因为减去一个正数不会让一个数更大,所以我在右边减去一个数不会让不等号方向改变
于是可得(x_i-lfloor px_i
floor>=x_j+c-lfloor p(x_j-c)
floor-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("
");
for(int i=1;i<=n+m;i++){
int x=find();
if(i%t==0)printf("%d ",x+sum);
}
return 0;
}