这个题我花了1h过掉了luogu的数据,花了大概2h过掉了牛客的数据,因为做法貌似跟题解的做法都不太一样,所以写一篇题解。
看完这题以后,我首先想到的是海盗分金子的那个模型,猜测,回合和回合之间的决策之间会产生一条条影响的"链",例如,如果模拟完以后的结果是这样的:
|回合| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |最大| 9 | 8 | 7 | 9 | 6 | * | * | * | * | |最小| 1 | 2 | 3 | 4 | 9 | * | * | * | * |
那么我们单独看第4回合和第5回合。
第5回合中,死亡的蛇9,上一次决策的回合是4。
那么,回合5跟回合4就产生了"联系",具体来说就是,第9条蛇,为了避免死亡,可能会在第4回合选择结束游戏。
进一步考虑,这些联系一定是若干条链(因为每个蛇只会被吃一次)。
那么我们随便拿一条链出来:
3-8-12-22
第22回合的蛇,一定会选择无脑吃,因为后续没有蛇会吃它。
那么第12回合的蛇,一定不敢吃,否则它会在22回合挂掉。
那么第8回合的蛇,一定敢吃,因为12回合的蛇不敢吃。
...
初步发现的这个结论,感觉很有用,于是写了个暴力,去把每条链生成出来,然后模拟一遍求出编号最小的“结束”回合。发现,在luogu过了55分(预期分数),牛客数据只过了20(n<=3)。
紧接着构造了一个反例:
链1:3-8 链2:4-5
发现,3-8之间,存在一条别的链4-5,那么,4-5之间必定有一条蛇要强行结束游戏,所以3其实是可以无脑吃的,也就是说:不能有边和边相交的情况。思考了一下后,在模拟的过程维护上一个链的端点即可。
修了这个锅以后,在luogu和牛客都过了55,证明结论大部分情况下对了(实际上感觉还有锅,等被hack再说吧)。
接下来改log速度的模拟大家都会,就不提了。
然后就考虑如何AC。
我并不擅长证明单调性之类的,于是考虑到这样一个事情:每次做决策后的蛇,在大多数情况下,比之前做过决策的蛇要短。
那么,维护两个队列,一个队列就是原始的蛇,一个队列是做过决策以后的蛇,每次做完决策以后,把新的长度用插入排序的方式从后面一步步换到前面去,直觉上看这个过程约等于O(1)的。
于是,在这次修改之后,终于通过了牛客和luogu的所有数据。
值得一提的是,luogu的数据貌似对这个做法强度不太够,不考虑链相交的情况,同时不进行插排的过程,无脑把新的蛇丢到队列末尾,也还是能AC。
代码因为是一步步改出来的,所以非常丑,见谅。
#include<bits/stdc++.h> #define maxn 1000005 using namespace std; int a[maxn]; int T,n,k,x,y; int b[maxn<<1],c[maxn<<1]; int idb[maxn<<1],idc[maxn<<1]; int ans[maxn],pre[maxn]; int link[maxn]; int head1,head2,tail1,tail2; void out() { cout<<"******"<<endl; // cout<<head1<<" "<<tail1<<" "<<head2<<" "<<tail2<<endl; for (int i=head1;i<=tail1;i++) cout<<b[i]<<"("<<idb[i]<<")"<<" "; cout<<endl; for (int i=head2;i<=tail2;i++) cout<<c[i]<<"("<<idc[i]<<")"<<" "; cout<<endl; } void work() { for (int i=1;i<=n;i++) pre[i]=link[i]=0; for (int i=1;i<=n;i++) b[n-i+1+maxn]=a[i],idb[n-i+1+maxn]=i; head1=1+maxn,tail1=n+maxn; head2=1+maxn,tail2=0+maxn; int shang=-1; for (int turn=1;turn<n;turn++) { // out(); int zd,zx; int zuida,zuixiao; int zd1,zd2,zx1,zx2; int zh1,zh2,xh1,xh2; if (head1<=tail1) zd1=b[head1],zx1=b[tail1],zh1=idb[head1],xh1=idb[tail1]; else zd1=-1,zx1=999999999; if (head2<=tail2) zd2=c[head2],zx2=c[tail2],zh2=idc[head2],xh2=idc[tail2]; else zd2=-1,zx2=999999999; //拿出来最大的 if (zd2>zd1 || (zd2==zd1 && zh2>zh1)) { zd=idc[head2]; zuida=zd2; head2++; } else { zd=idb[head1]; zuida=zd1; head1++; } //拿出来最小的 if (zx2<zx1 || (zx2==zx1 && xh2<xh1)) { zx=idc[tail2]; zuixiao=zx2; tail2--; } else { zx=idb[tail1]; zuixiao=zx1; tail1--; } if (pre[zx]!=0) { if (shang==-1 || shang==pre[zx]) { link[turn]=pre[zx]; } else break; shang=turn; } //最大的人这回合杀人了 pre[zd]=turn; //减去血量 int tt=zuida-zuixiao; int id=zd; tail2++; c[tail2]=tt; idc[tail2]=zd; int now=tail2; while (now>head2) { if (c[now]<c[now-1] || (c[now]==c[now-1] && idc[now]<idc[now-1])) break; swap(c[now],c[now-1]); swap(idc[now],idc[now-1]); now--; } } int zuixiao=n; for (int i=n;i>=1;i--) if (link[i]) { int now=i,flag=0; while (now!=0) { if (flag==1) zuixiao=min(zuixiao,now); int pp=now; now=link[now]; link[pp]=0; flag=1-flag; } } cout<<n-zuixiao+1<<endl; return; } int main() { scanf("%d",&T); scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); work(); T--; while (T--) { scanf("%d",&k); while (k--) { scanf("%d%d",&x,&y); a[x]=y; } work(); } }