P7078 [CSP-S2020] 贪吃蛇 题解
这道题我调了好久......细节非常多。
首先,看完题目后,可以发现这是一道博弈问题。
那么接下来,我们假设最强蛇为 \(x\) ,最弱蛇为 \(y\) ,那么这里就有两个结论:
- 如果 \(x\) 吃了 \(y\) 不是最弱的蛇,则 \(x\) 必吃 \(y\) 。
证明:假设当前第二强的蛇为 \(a\) ,第二弱的蛇为 \(b\) ,那么 \(x>a>b>y,x-y>b\) ,那么假设 \(x\) 吃了 \(y\) 之后变成了 \(z\) ,那么此时有 \(z>b\) (这里 \(z\) 和 \(a\) 的大小不能确定,因为有可能最强蛇吃掉最弱蛇还是最强蛇) ,如果 \(z>a\) ,那么 \(z\) 还是最强蛇,所以 \(x\) 肯定要吃掉 \(y\) ,毕竟吃完后选择权还在 \(x\) 手上;如果 \(a>z\) ,那么 \(a>z=x-y>b\) ,由于 \(x>a>b>y\) ,假设 \(a\) 吃了 \(b\) 之后变成了 \(c\) ,那么有 \(z>c\) 。因此,如果 \(c\) 不死,那么 \(z\) 不会死,可以放心吃;否则,\(a\) 为了活命,不能吃掉 \(b\) 变成 \(c\) ,因此此时 \(x\) 仍然可以吃 \(y\) 。
所以综上,如果 \(x\) 吃了 \(y\) 不是最弱的蛇,则 \(x\) 必吃 \(y\) 。 - 如果 \(x\) 吃了 \(y\) 之后变成最弱的蛇了,那么要不要吃呢?
此时,我们就不能看 \(x\) 的选择了,而要看下一条蛇的选择。此处令 \(f\) 为第二强的蛇,\(g\) 为第三强的蛇,如果 \(x\) 吃了 \(y\) 变成 \(z\) ,那么 \(z\) 能不能活看 \(f\) 。如果 \(f\) 吃了 \(z\) ,那么 \(x\) 不能吃 \(y\) ;否则,\(f\) 不吃 \(z\) 那么 \(x\) 可以吃 \(y\)。那么 \(f\) 吃不吃又是看谁的呢?假设 \(f\) 吃掉最弱蛇不是最弱蛇,由上述结论 \(f\) 可以吃最弱蛇;否则,这个问题就变成了 “如果 \(f\) 吃了最弱蛇后变成了最弱蛇,那么要不要吃呢?”此时,我们惊喜的发现这个问题变成了一个 递归问题 !那么 \(f\) 吃不吃完全看 \(g\) 的选择, 可以推出:\(g\) 吃 \(f\) 不吃 \(x\) 吃,\(g\) 不吃 \(f\) 吃 \(x\) 不吃。因此,我们在程序中不断递归就好,假设当前第 \(k\) 强的蛇吃,那么 \(k-1\) 不吃,\(k-2\) 吃,\(k-3\) 不吃......直到第 1 强的蛇为止。
因此思路就是:先运行结论 1 ,无法运行时根据结论 2 的结果判断是否还能再吃 1 次,最后输出答案。
而由于原序列已经有序,因此这道题我们可使用两个双端队列 \(q1,q2\) 维护。首先所有蛇入队 \(q1\) ,运行结论 1 时所有生成的新蛇从对头入队 \(q2\) ,每一次取出最大值和最小值判断。当没办法再运行结论 1 时,跳至结论 2 递归再看一次能否再吃 1 条。这样,时间复杂度为 \(O(Tn)\) ,可以通过此题。
上代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
int t,n,a[MAXN],q1[MAXN][2],q2[MAXN][2],l1,l2,r1,r2,ans;//ans=被吃了几条蛇
//q1[i][0]=q2[i][0]=值,q1[i][1]=q2[i][1]=编号
struct node
{
int flag,sum,id;
};
int read()
{
int sum=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') {sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
return sum;
}
node Get_Max()//取出最大值
{
int sum=0,flag=1,id=0;
if(l2<=r2)
{
if(q2[r2][0]>=sum) {sum=q2[r2][0];id=q2[r2][1];flag=2;}
}
if(l1<=r1)
{
if(q1[r1][0]>sum) {sum=q1[r1][0];id=q1[r1][1];flag=1;}
else if(q1[r1][0]==sum&&q1[r1][1]>id) {sum=q1[r1][0];id=q1[r1][1];flag=1;}
}
if(flag==1) r1--;
else r2--;
return (node){flag,sum,id};
}
node Get_Min()//取出最小值
{
int sum=0x7f7f7f7f,flag=1,id=0;
if(l2<=r2)
{
if(q2[l2][0]<=sum) {sum=q2[l2][0];id=q2[l2][1];flag=2;}
}
if(l1<=r1)
{
if(q1[l1][0]<sum) {sum=q1[l1][0];id=q1[l1][1];flag=1;}
else if(q1[l1][0]==sum&&q1[l1][1]<id) {sum=q1[l1][0];id=q1[l1][1];flag=1;}
}
if(flag==1) l1++;
else l2++;
return (node){flag,sum,id};
}
void Solve1()//结论 1
{
while(1)
{
if(n-ans==1) return ;//注意边界条件
node x=Get_Max(),y=Get_Min();
node z=Get_Min();
if(x.sum-y.sum>z.sum||(x.sum-y.sum==z.sum&&x.id>z.id))//能吃
{
ans++;
q2[--l2][0]=x.sum-y.sum;q2[l2][1]=x.id;
if(l1<=r1&&(z.sum<q1[l1][0]||(z.sum==q1[l1][0]&&z.id<q1[l1][1]))) {q1[--l1][0]=z.sum;q1[l1][1]=z.id;}
else {q2[--l2][0]=z.sum;q2[l2][1]=z.id;}
continue;
}
else//不能吃
{
if(x.flag==1) {q1[++r1][0]=x.sum;q1[r1][1]=x.id;}else {q2[++r2][0]=x.sum;q2[r2][1]=x.id;}
if(z.flag==1) {q1[--l1][0]=z.sum;q1[l1][1]=z.id;}else {q2[--l2][0]=z.sum;q2[l2][1]=z.id;}
if(y.flag==1) {q1[--l1][0]=y.sum;q1[l1][1]=y.id;}else {q2[--l2][0]=y.sum;q2[l2][1]=y.id;}//塞回队列里面
return ;
}
}
}
bool Slove2(int last)//结论 2
{
if(last==0) return 0;
if(last==1) return 0;
if(last==2) return 1;//注意边界条件
node x=Get_Max();node y=Get_Min();node z=Get_Min();
if(x.sum-y.sum>z.sum||(x.sum-y.sum==z.sum&&x.id>z.id)) return 1;//结论 1
else
{
if(l1<=r1&&(z.sum<q1[l1][0]||(z.sum==q1[l1][1]&&q1[l1][1]>z.id))) {q1[--l1][0]=z.sum;q1[l1][1]=z.id;}
else {q2[--l2][0]=z.sum;q2[l2][1]=z.id;}
if(l1<=r1&&(x.sum-y.sum<q1[l1][0]||(x.sum-y.sum==q1[l1][0]&&q1[l1][1]>x.id))) {q1[--l1][0]=x.sum-y.sum;q1[l1][1]=x.id;}
else {q2[--l2][0]=x.sum-y.sum;q2[l2][1]=x.id;}
return !Slove2(last-1);//递归实现
}
}
int main()
{
t=read();
bool flag=0;
while(t--)
{
ans=0;//多测不清空,爆零两行泪
memset(q1,0,sizeof(q1));
memset(q2,0,sizeof(q2));
if(!flag)
{
n=read();flag=1;
for(int i=1;i<=n;i++) a[i]=read();
}
else
{
int k=read();
for(int i=1;i<=k;i++)
{
int x,y;x=read();y=read();a[x]=y;
}
}
l1=1;r1=0;l2=n+1;r2=n;
for(int i=1;i<=n;i++) q1[++r1][0]=a[i];
for(int i=1;i<=n;i++) q1[i][1]=i;
Solve1();
if(Slove2(n-ans)) ans++;//根据结论 2 看看能不能再吃一条
cout<<n-ans<<"\n";//输出存活蛇
}
return 0;
}