很有意思的一道题,感觉csp2020的T3和T4都比较有质量,属于考思维的题目。
题目链接:
https://www.luogu.com.cn/problem/P7078
题目大意:
有n条蛇,每条蛇都有一个体力值a[i]还有一个编号(1~n的编号)。这些蛇需要决斗,每轮决斗都是最强蛇考虑要不要吃掉最弱蛇。蛇的强弱我们以他们剩余的体力值作为判断,体力越多越强。假设有两条蛇体力相同,则编号大的更强。
那么对于每轮决斗,最强蛇有两种决策:
1.如果选择吃,那么实力最强的蛇的体力值将减去实力最弱的蛇的体力值,实力最弱的蛇被吃掉,退出接下来的决斗。之后开始下一轮决斗。
2.如果选择不吃,决斗立刻结束。
每条蛇希望在自己不被吃的前提下在决斗中尽可能多吃别的蛇(显然,蛇不会选择吃自己)。
现在假设每条蛇都足够聪明,请你求出决斗结束后会剩几条蛇。
n<=10^6
注意数据有T组,T<=10
思路:
人不如蛇系列。
看到题目,感觉是博弈啊。
我们从2条蛇开始考虑。假设n=2,必吃
假设n=3,我们有a1,a2,a3.那么若a3-a1<a2则不吃,否则吃。
那么n>3的时候呢?似乎不好判断。
但是对于n来说,如果n-1不吃,那么当前肯定吃了。否则就得考虑是否要冒险吃。
我这里一开始直接写了个递归,先考虑每次都吃,然后到只剩2条蛇的时候,往上返回结果。
那么假设dfs(n)表示还剩n条蛇的时候吃不吃,true表示吃,false表示不吃。
若dfs(n-1)为true,那么如果吃完变最弱的则返回false并且记录max(因为递归中已经知道下一次会吃了,所以如果此时冒险吃变成最弱,那就选择不吃),否则返回true(冒险吃)
若dfs(n-1)为false,那么dfs(n)返回true
那么我只要得到最大的不吃(即过程中记录的max)就是答案。
这样做可以拿40分。
#include<bits/stdc++.h> using namespace std; struct node{ int a,idx; }p[1000030],q[1000030]; int ans=0; bool dfs(int n){ if(n==2)return true; node w[20030]; for(int i=1;i<=n;i++)w[i].a=q[i].a,w[i].idx=q[i].idx; node tmp; tmp.a=q[n].a-q[1].a;tmp.idx=q[n].idx; for(int i=n-1;i>1;i--){ //利用插入排序的方法插入元素 if(tmp.a<q[i].a)q[i+1].a=q[i].a,q[i+1].idx=q[i].idx; else { q[i+1].a=tmp.a; q[i+1].idx=tmp.idx; break; } } for(int i=1;i<n;i++){ q[i].a=q[i+1].a; q[i].idx=q[i+1].idx; } if(dfs(n-1)){ if(w[n].a-w[1].a<w[2].a||(w[n].a-w[1].a==w[2].a&&w[n].idx<w[2].idx)){ans=max(ans,n);return false;} else return true; } else return true; } int main() { int T,n; cin>>T; cin>>n; for(int i=1;i<=n;i++)cin>>p[i].a,p[i].idx=i; for(int i=1;i<=n;i++)q[i].a=p[i].a,q[i].idx=p[i].idx; while(T--){ for(int i=1;i<=n;i++)q[i].a=p[i].a,q[i].idx=p[i].idx; ans=1; dfs(n); cout<<ans<<endl; int k; cin>>k; for(int i=1;i<=k;i++){ int x,y; cin>>x>>y; p[x].a=y; } } return 0; }
其实上面的算法,其中一个不放心的地方是冒险吃的时候。在上面的递归中,我们的做法是如果可以冒险,每次都选择冒险吃,由于是递归,所以返回来的时候就可以得到结果然后再次判断是否要吃或不吃。这么看来,感觉是没什么问题的。
看了正解,有一个比较重要的点需要证明的:
如果当前吃完以后不是最弱的,放心吃!
证明:若有a1<a2<...a(n-1)<an,那么an-a1>a(n-1)-a2
这意味着an吃完一次以后还是比a(n-1)吃完一次强。
即如果an吃掉a1以后不是最弱,a(n-1)不管选择吃不吃,an都可以不死。假设a(n-1)选择吃,那么an比a(n-1)还是强。假设a(n-1)选择不吃,本轮决斗结束,an不死。
也就是,我们一开始的时候可以直接模拟,如果当前吃了不是最弱的,可以一直模拟吃下去。
接下来考虑当前ai吃了变成最弱的时候
我们再分情况讨论一下,假设下一条蛇是a(i-1):
①下一条蛇不吃,当前吃。
②下一条蛇吃,当前不吃
那么下一条蛇a(i-1)吃不吃呢?他考虑的问题其实和a(i)一样的,取决于下下条蛇。问题又变成了一个递归,那么什么时候结束呢?
(1)只剩2条蛇的时候(2)某一条蛇直接放心吃的时候
所以存在一个奇偶性的情况了。
我们把问题分成3个阶段:
阶段1:所有蛇放心吃阶段,吃了不是最弱。
阶段2:所有蛇冒险吃阶段,吃了是最弱。此时每条蛇吃不吃仅仅取决于他的下面那条蛇,因此一定是吃-不吃交替的。
阶段3:某一条蛇可以放心吃(只剩2条或者吃了不是最弱)
那么从阶段3到阶段2,中间的蛇的数量的奇偶性影响了阶段2的第一条冒险的蛇会不会多吃一次。
对于上述过程,我们可以利用set进行模拟。每次取出最大最小来进行情况的判断,这样由于set的存在,复杂度为O(nlogn)。可以得到70分。
#include<bits/stdc++.h> using namespace std; int n; pair<int,int>p[1000030]; set<pair<int,int> >st; int main() { int T; cin>>T; cin>>n; for(int cas=1;cas<=T;cas++){ if(cas==1){ for(int i=1;i<=n;i++){ int x; cin>>x; p[i]=make_pair(x,i); st.insert(p[i]); } } else { int k; cin>>k; for(int i=1;i<=k;i++){ int x,y; cin>>x>>y; p[x]=make_pair(y,x); } st.clear(); for(int i=1;i<=n;i++)st.insert(p[i]); } int flag=0; int ans; while(1){ if(st.size()==2){ //只剩两条,此时必吃。 st.erase(st.begin()); if(flag){ ans=ans-(ans-st.size())%2; //看阶段2的奇偶性 } else ans=1; break; } set<pair<int,int> >::iterator it; it=st.end();it--; st.erase(it); pair<int,int>p1,pn; pn=*it; it=st.begin();p1=*it; st.erase(it++); st.insert(make_pair(pn.first-p1.first,pn.second)); if(st.begin()->second==pn.second){ //进入阶段2冒险吃 if(!flag)ans=st.size()+1,flag=1; } else { //放心吃阶段1和3 if(flag){ //阶段3 ans=ans-(ans-st.size())%2; break; } } } cout<<ans<<endl; } return 0; }
正解是使用2个双端队列模拟,由于我不会双端队列,先贴个代码:
#include<bits/stdc++.h> using namespace std; int n; pair<int,int>p[1000030]; set<pair<int,int> >st; int main() { int T; cin>>T; cin>>n; for(int cas=1;cas<=T;cas++){ deque<pair<int,int > >dq1,dq2; if(cas==1){ for(int i=1;i<=n;i++){ int x; cin>>x; p[i]=make_pair(x,i); dq1.push_back(p[i]); } } else { int k; cin>>k; for(int i=1;i<=k;i++){ int x,y; cin>>x>>y; p[x]=make_pair(y,x); } for(int i=1;i<=n;i++)dq1.push_back(p[i]); } int ans; while(1){ if(dq1.size()+dq2.size()==2){ ans=1; break; } int p1,pn,id; p1=dq1.front().first;dq1.pop_front(); if(dq2.empty()||!dq1.empty()&&dq1.back()>dq2.back()){ pn=dq1.back().first; id=dq1.back().second; dq1.pop_back(); } else { pn=dq2.back().first; id=dq2.back().second; dq2.pop_back(); } pair<int,int>pp=make_pair(pn-p1,id); if(dq1.empty()||dq1.front()>pp){ //进入第二阶段 ans=dq1.size()+dq2.size()+2; int cnt=0; while(1){ cnt++; if(dq1.size()+dq2.size()+1==2){ ans-=(cnt+1)%2; break; } int x,id; if(dq2.empty()||!dq1.empty()&&dq1.back()>dq2.back()){ x=dq1.back().first; id=dq1.back().second; dq1.pop_back(); } else{ x=dq2.back().first; id=dq2.back().second; dq2.pop_back(); } pp=make_pair(x-pp.first,id); if((dq1.empty()||pp < dq1.front())&&(dq2.empty()||pp < dq2.front())); else { ans-=(cnt+1)%2; break; } } break; } else { dq2.push_front(pp); //放心吃的第一阶段 } } cout<<ans<<endl; } return 0; }