P7078 [CSP-S2020] 贪吃蛇 题解

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

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

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

  1. 如果 x x x 吃了 y y y 不是最弱的蛇,则 x x x 必吃 y y y
    证明:假设当前第二强的蛇为 a a a ,第二弱的蛇为 b b b ,那么 x > a > b > y , x − y > b x>a>b>y,x-y>b x>a>b>y,xy>b ,那么假设 x x x 吃了 y y y 之后变成了 z z z ,那么此时有 z > b z>b z>b (这里 z z z a a a 的大小不能确定,因为有可能最强蛇吃掉最弱蛇还是最强蛇) ,如果 z > a z>a z>a ,那么 z z z 还是最强蛇,所以 x x x 肯定要吃掉 y y y ,毕竟吃完后选择权还在 x x x 手上;如果 a > z a>z a>z ,那么 a > z = x − y > b a>z=x-y>b a>z=xy>b ,由于 x > a > b > y x>a>b>y x>a>b>y ,假设 a a a 吃了 b b b 之后变成了 c c c ,那么有 z > c z>c z>c 。因此,如果 c c c 不死,那么 z z z 不会死,可以放心吃;否则, a a a 为了活命,不能吃掉 b b b 变成 c c c ,因此此时 x x x 仍然可以吃 y y y
    所以综上,如果 x x x 吃了 y y y 不是最弱的蛇,则 x x x 必吃 y y y
  2. 如果 x x x 吃了 y y y 之后变成最弱的蛇了,那么要不要吃呢?
    此时,我们就不能看 x x x 的选择了,而要看下一条蛇的选择。此处令 f f f 为第二强的蛇, g g g 为第三强的蛇,如果 x x x 吃了 y y y 变成 z z z ,那么 z z z 能不能活看 f f f 。如果 f f f 吃了 z z z ,那么 x x x 不能吃 y y y ;否则, f f f 不吃 z z z 那么 x x x 可以吃 y y y。那么 f f f 吃不吃又是看谁的呢?假设 f f f 吃掉最弱蛇不是最弱蛇,由上述结论 f f f 可以吃最弱蛇;否则,这个问题就变成了 “如果 f f f 吃了最弱蛇后变成了最弱蛇,那么要不要吃呢?”此时,我们惊喜的发现这个问题变成了一个 递归问题 !那么 f f f 吃不吃完全看 g g g 的选择, 可以推出: g g g f f f 不吃 x x x 吃, g g g 不吃 f f f x x x 不吃。因此,我们在程序中不断递归就好,假设当前第 k k k 强的蛇吃,那么 k − 1 k-1 k1 不吃, k − 2 k-2 k2 吃, k − 3 k-3 k3 不吃…直到第 1 强的蛇为止。

因此思路就是:先运行结论 1 ,无法运行时根据结论 2 的结果判断是否还能再吃 1 次,最后输出答案。

而由于原序列已经有序,因此这道题我们可使用两个双端队列 q 1 , q 2 q1,q2 q1,q2 维护。首先所有蛇入队 q 1 q1 q1 ,运行结论 1 时所有生成的新蛇从对头入队 q 2 q2 q2 ,每一次取出最大值和最小值判断。当没办法再运行结论 1 时,跳至结论 2 递归再看一次能否再吃 1 条。这样,时间复杂度为 O ( T n ) O(Tn) 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;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值