1. 程式人生 > 其它 >P7078 [CSP-S2020] 貪吃蛇 題解

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;
}