P7078 [CSP-S2020] 貪吃蛇 題解
阿新 • • 發佈:2022-04-12
這道題我調了好久......細節非常多。
首先,看完題目後,可以發現這是一道博弈問題。
那麼接下來,我們假設最強蛇為 \(x\) ,最弱蛇為 \(y\) ,那麼這裡就有兩個結論:
- 如果 \(x\) 吃了 \(y\) 不是最弱的蛇,則 \(x\) 必吃 \(y\) 。
證明:假設當前第二強的蛇為 \(a\) ,第二弱的蛇為 \(b\) ,那麼 \(x>a>b>y,x-y>b\) ,那麼假設 \(x\) 吃了 \(y\) 之後變成了 \(z\) ,那麼此時有 \(z>b\) (這裡 \(z\) 和 \(a\) 的大小不能確定,因為有可能最強蛇吃掉最弱蛇還是最強蛇) ,如果 \(z>a\)
所以綜上,如果 \(x\) 吃了 \(y\) 不是最弱的蛇,則 \(x\) - 如果 \(x\) 吃了 \(y\) 之後變成最弱的蛇了,那麼要不要吃呢?
此時,我們就不能看 \(x\) 的選擇了,而要看下一條蛇的選擇。此處令 \(f\) 為第二強的蛇,\(g\) 為第三強的蛇,如果 \(x\) 吃了 \(y\) 變成 \(z\) ,那麼 \(z\) 能不能活看 \(f\) 。如果 \(f\) 吃了 \(z\) ,那麼 \(x\) 不能吃 \(y\) ;否則,\(f\) 不吃 \(z\) 那麼 \(x\) 可以吃 \(y\)。那麼 \(f\) 吃不吃又是看誰的呢?假設 \(f\) 吃掉最弱蛇不是最弱蛇,由上述結論 \(f\) 可以吃最弱蛇;否則,這個問題就變成了 “如果 \(f\)
因此思路就是:先執行結論 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;
}