P7078 [CSP-S2020] 贪吃蛇 题解

这道题我调了好久......细节非常多。

首先,看完题目后,可以发现这是一道博弈问题。

那么接下来,我们假设最强蛇为 \(x\) ,最弱蛇为 \(y\) ,那么这里就有两个结论:

  1. 如果 \(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\)
  2. 如果 \(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;
}
posted @ 2022-04-12 21:43  Plozia  阅读(98)  评论(0编辑  收藏  举报