P7078 || [CSP-S2020] 贪吃蛇
省队学长给我们拉的模拟赛的 T4,没想到还能交题解,这下不得不写我的第一篇黑题题解来纪念了。
思路
70pts
先引出结论:当最大的蛇吃了后没有变成最小的蛇,它一定会吃;否则需要递推进行决策。
下面解释为什么一定会吃。令初始时蛇的能力为
而当最大蛇吃掉后变成了最小蛇,若次大蛇经过一系列头脑风暴后选择不吃,那它就会白吃一回合;否则它不吃。正着决策显然困难,所以选择倒着推回去。
我们可以使用 set 维护这一过程。具体地,先假设所有蛇都不聪明,每条蛇都一定会吃,模拟一轮后可以得到每轮最大的蛇
100pts
上述做法中瓶颈在于 set 的时间复杂度,我们考虑能否用线性结构进行维护。使用双端队列真是再合适不过了!
具体地,开两个双端队列
这一轮进食完毕后的蛇的能力值一定是
-
若
min_i=st1_r ,那么一定符合单调性:若min_{i-1} 来自st1 ,那么一定有min_{i-1}\le min_i ,因此此轮能力值一定小于上一轮;若min_{i-1} 来自st2 ,那么说明它比st1_r 后面的那个值大,因此依旧有min_{i-1}\le min_i ,于是同理。 -
若
min_i=st2_r ,那就不一定符合单调性了。如果符合单调性就依旧满足,否则的那种情况会在下面提到。
对于出现不满足单调性的情况,那就只能是当前值比
代码
#include<set>
#include<queue>
#include<iostream>
#define int long long
using namespace std;
const int N=1e6+5;
int _anll_,t;
int n,m,ans,tim,A[N],del[N],eat[N],beat[N];
deque<pair<int,int> > st1,st2;
void solve(bool _anll_){
if(!_anll_){
cin>>n;
for(int i=1;i<=n;i++) cin>>A[i];
}
else{
cin>>m;int a,b;
while(m--) cin>>a>>b,A[a]=b;
}
tim=-1,ans=1;
while(st1.size()) st1.pop_back();
while(st2.size()) st2.pop_back();
for(int i=1;i<=n;i++)
st1.push_back({A[i],i}),
eat[i]=beat[i]=del[i]=0;
for(int i=n;i>1;i--){
pair<int,int> mx,mn,an;
if(st1.size()&&st2.size()){
if(st1.back()>st2.back())
{mx=st1.back();st1.pop_back();}
else {mx=st2.back();st2.pop_back();}
}
else if(st1.size())
{mx=st1.back();st1.pop_back();}
else {mx=st2.back();st2.pop_back();}
if(st1.size()&&st2.size()){
if(st1.front()<st2.front())
{mn=st1.front();st1.pop_front();}
else {mn=st2.front();st2.pop_front();}
}
else if(st1.size())
{mn=st1.front();st1.pop_front();}
else {mn=st2.front();st2.pop_front();}
beat[i]=mn.second,eat[i]=mx.second;
an={mx.first-mn.first,mx.second};
if(st2.size()&&an>st2.front()){
ans=tim=i;break;
}
st2.push_front(an);
}
if(tim==-1) tim=2;
for(int i=tim;i<=n;i++){
int x=eat[i];
if(del[x]>=ans) ans=i;
else del[beat[i]]=i;
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>t;
for(int i=0;i<t;i++) solve(i);
return 0;
}