[CSP-S2020] 贪吃蛇题解
前言:
我在洛谷通过的第三道黑题······不容易啊,离第二道黑题 AC 已经过了三四个月了。
写篇题解留念吧。
这些蛇是真的牛逼,怎么都那么聪明啊(比我聪明就对了)。
正文:
这题代码倒不算长,我打了 2.2 kb 这样,算法也没什么,不过思维难度极高。
基本思路:
有个很明显的结论:由于每条蛇都足够聪明,所以选择权在强的蛇手里,因为它们都不想自己死,所以强的蛇一定不会比弱的蛇先死。
首先在某个阶段,分类讨论一波:
-
如果最牛的那条蛇(记为俞大仙)吃了最菜的那条蛇(记为俞菜鸡)后仍是最厉害的,当然不吃白不吃,决斗继续。
-
但如果俞大仙之后不是最厉害的呢,就要继续分类讨论:
-
如果俞大仙吃完后自己不是最菜的,此时最厉害的蛇(俞小仙)比俞大仙(吃俞菜鸡前)弱,最弱的蛇比俞菜鸡强,所以俞小仙如果选择吃的话就会比俞大仙弱,根据上面的结论,会死在俞大仙前面,那么俞小仙就不会吃,俞大仙就是安全的,所以这样的话俞大仙仍然可以选择吃。
-
根据上文所述,有“如果最强的蛇吃完后不是最弱的,就吃”的结论,我们现在需要考虑俞大仙如果成为最弱蛇的情况(最复杂的)。若吃,且俞小仙吃俞大仙后不是最弱的,那么就会把俞大仙干掉,因为俞大仙足够聪明,所以这样他就不会吃。如果俞小仙吃俞大仙成为最弱的,而之后的最强蛇鱼小鲜吃俞小仙不是最弱的,那么俞小仙危险,则不吃,俞大仙安全,所以这样俞大仙就会吃。如果鱼小鲜吃俞小仙是最弱的······我们可以发现,如果俞大仙成为最弱的,就有了一个递归问题,那么他吃不吃俞菜鸡就与之后遍历到的第一个吃完不最弱的蛇的奇偶性有关(其实奇偶性指俞大仙与那条蛇间隔几条蛇,这里直接写奇偶性是方便理解)。
-
只是什么时候决斗结束呢?很显然,只剩两条蛇决斗时,强的蛇成为永远的霸主,决斗结束;还有一种则是题目中所说当最强的蛇不选择决斗时,也结束。上面分析出来的东西可以推出,不选择决斗,当且仅当最强蛇吃完后成为最弱的。利用奇偶性分析得到他不选择吃时决斗自然结束,若选择吃呢?如果选择吃,说明他的后一条蛇不选择吃,即如果最强蛇吃完成为最弱的,决斗将在本轮或下轮结束。
复杂度优化:
结束了吗???
不,还没有!!!
上面那个思路是完成正确的,但如果你按照它去敲,会惊讶的发现只能得 70 分。因为用 set 或 map 维护最强最弱蛇的复杂度是 \(O(n \log n)\),还有些常数,再带上个 T,时间就炸了。
正解是用 两个具有单调性的双端队列维护。
弄两个双端队列 q1,q2。先把初始的元素及下标一对一对弄到 q1 里面,由题目里面说的,易知 q1 单调不递减。接下来再维护一个单调不递减的 q2。(记录决斗过的蛇)。
因为两个队列都是单调不递减的,所以当前阶段最强蛇就是两个队列队尾的较大值,最弱蛇则同理,从队首取。之前说了,如果最强蛇吃了后会变最弱,决斗将在本轮或下轮结束,就直接处理,输出答案。如果不是最弱,说明决斗将继续,就要把最强蛇弄回队列,并且要推到 q2 头部。
现在证明推到 q2 头部不影响 q2 单调性的正确性:之前也提到过,因为当前阶段与之前阶段的蛇相比,最强的肯定越来越弱,最弱的越来越强,所以最强的吃最弱的后也不如之前阶段决斗的胜者强,所以是 q2 中最小的。
可见维护双端队列是 \(O(1)\) 的,把 \(O(\log n)\) 优化到了 \(O(1)\),总时间复杂度也优化到了 \(O(Tn)\)。
Code(知道你们最喜欢这个):
#include<bits/stdc++.h>
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define inf 0x7fffffff
#define INF 0x3f
#define v e[i].y
using namespace std;
inline ll read(){
char ch=getchar();ll x=0,w=1;
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-48,ch=getchar();return w==1?x:-x;
}
inline void write(ll x){
if(x<0)x=-x,putchar('-');
if(x<10){putchar(48+x);return;}
write(x/10),putchar((x+10)%10+48);
}
typedef pair<int,int> pi;
int T=read(),n,a[1000005],K;
void solve(){
pi o1,o2,o3;
deque<pi >q1,q2;
for(int i=1;i<=n;i++)q1.push_back(make_pair(a[i],i));
while(1){
if(q1.size()+q2.size()==2){
printf("1");break;
}
if(q1.size()&&q2.size()){
if(q1.back()<q2.back())o1=q2.back(),q2.pop_back();
else o1=q1.back(),q1.pop_back();
}
else if(q1.size())o1=q1.back(),q1.pop_back();
else if(q2.size())o1=q2.back(),q2.pop_back();
o2=q1.front(),q1.pop_front(),o3=make_pair(o1.first-o2.first,o1.second);
if((q1.size()&&o3>q1.front())||(q2.size()&&o3>q2.front())){q2.push_front(o3);continue;}
ll ans=q1.size()+q2.size()+2;
bool d=0;
while(1){
d^=1;
if(q1.size()+q2.size()==1){
if(d==0)ans--;
break;
}
if(q1.size()&&q2.size()){
if(q1.back()<q2.back())o1=q2.back(),q2.pop_back();
else o1=q1.back(),q1.pop_back();
}
else if(q1.size())o1=q1.back(),q1.pop_back();
else if(q2.size())o1=q2.back(),q2.pop_back();
o3=make_pair(o1.first-o3.first,o1.second);
if((q1.size()&&o3>q1.front())||(q2.size()&&o3>q2.front())){
if(d==0)ans--;
break;
}
}
write(ans);
break;
}
putchar('\n');
}
int main(){
for(int tt=1;tt<=T;tt++){
if(tt==1){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
}
else{
K=read();
for(int i=1,j,k;i<=K;i++)j=read(),k=read(),a[j]=k;
}
solve();
}
return 0;
}