CSP-S2020 贪吃蛇
参考博客:
https://qaq-am.com/CSPS-2020-贪吃蛇/
有\(n\)条蛇,执行若干次这样的操作:最强的蛇(以能力值为第一关键字,编号为第二关键字)有选择是否执行的权力,如果不执行就结束,如果执行就把最弱的蛇吃掉,并且自己的能力值变成两者之间的差。
假设所有的蛇都绝顶聪明,它们希望吃尽量多的蛇而且自己不被吃,问最终剩下多少蛇。
\(T\le 10,n\le 10^6\)
保证一开始给出的序列单调递增。
先讲一个读完题意就会的\(70\)分做法:由于每条蛇可以预知未来,所以它可以推演它选择吃之后的情况,推演出来如果它会死,那么就选择不吃以改变这种情况,否则选择吃。然后就直接模拟:先假设所有的蛇失去了理智,一直互相吃到最后,然后把这个情况倒推回来。到某一条蛇选择的时候,如果后面它死了,就改变未来,即将当前情况到它做出选择的这个区间的操作都撤销掉。
于是这样最终的问题是:维护一个序列,每次取出最大值和最小值,作差后再丢进去。这个东西用set
可以随便做到\(O(n\lg n)\)。
优化这个维护的过程到\(O(n)\),目前我只知道有EI的做法是对的(当然我没有看懂)。
现在讲讲LK的做法:
考虑如果一条蛇做出选择之后它不是最小值,那么它尽管吃,因为下一个做出选择的蛇一定比它死得更快(因为做差后能力值更小)。
如果它是最小值呢?那么这时候它就预知未来,让蛇们失去理智互吃,直到出现一条蛇做出选择之后不是最小值(或者没有蛇了),这条蛇一定是会吃的;根据这个过程进行的步数的奇偶性进行分类讨论,决定它是否吃,如果不吃就结束,如果吃也会在它下一条做出决定的蛇结束。
这样这道题就做完了。
这个做法妙就妙在:它没有把蛇们失去理智互吃的全过程搞出来,而是通过我们只关心最终的正确结果而设计的算法,在可以得到正确结果的时候return
,避免了后面可能出现的问题。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 1000005
#define mp(x,y) make_pair(x,y)
int n;
pair<int,int> a[N],b[N];
int ah,at,bh,bt;
bool empty(){return !(ah<=at || bh<=bt);}
pair<int,int> gmin(){
if (ah<=at && a[ah]<b[bt] || bh>bt)
return a[ah];
return b[bt];
}
pair<int,int> pop_front(){
if (ah<=at && a[ah]<b[bt] || bh>bt)
return a[ah++];
return b[bt--];
}
pair<int,int> pop_back(){
if (ah<=at && a[at]>b[bh] || bh>bt)
return a[at--];
return b[bh++];
}
pair<int,int> doit1(){
pair<int,int> p=pop_front(),q=pop_back(),r;
// printf("(%d,%d) eats (%d,%d)\n",q.first,q.second,p.first,p.second);
r.first=q.first-p.first;
r.second=q.second;
return r;
}
void doit2(pair<int,int> &r){
pair<int,int> q=pop_back();
r.first=q.first-r.first;
r.second=q.second;
}
int work(){
int ans=n,i=1;
ah=1,at=n;
bh=1,bt=0;
for (;i<n;++i){
pair<int,int> r=doit1();
if (r<gmin() && !empty()){
int tmp=1;
for (++i;i<n;++i){
doit2(r);
tmp++;
if (r>gmin() && !empty()){
if (tmp&1^1)
return ans;
// assert(0);这个assert是假的,因为可能会出现值相同编号不同的问题。
return ans-1;
}
}
if (tmp&1^1)
return ans;
return ans-1;
}
else{
ans--;
b[++bt]=r;
// printf("(%d,%d)\n",r.first,r.second);
}
}
return ans;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
freopen("snakes.in","r",stdin);
freopen("snakes.out","w",stdout);
int T;
scanf("%d%d",&T,&n);
for (int i=1;i<=n;++i)
scanf("%d",&a[i].first),a[i].second=i;
printf("%d\n",work());
T--;
while (T--){
int k;
scanf("%d",&k);
for (int i=1;i<=k;++i){
int x,y;
scanf("%d%d",&x,&y);
a[x].first=y;
}
printf("%d\n",work());
}
return 0;
}