CSP2020-S T4 貪吃蛇
前言
靈感源自老K與EI的題解,有所融會貫通。所有的蛇都足夠聰明,就我挺笨的。
思路
首先這道題,跟16年的蚯蚓很像,至少我在考場上面一看第一個想的就是雙隊列優化,但是這個暫時不能說是對的,也沒錯。都無妨。為了方便,設所有蛇的實力為 \(a_1,a_2,...,a_{n-1},a_n\).
先來模擬一下吧。
提出 \(a_n,a_1\) 作為最大值和最小值。這里建議想成合並蛇。合並成了一條 \(a_n-a_1\) 的蛇(記為 \(a_x\))。將它放入蛇群中。
希望能夠每次盡量快的取出最大值和最小值,則能雙隊列優化是最好的。
現在目的是為了靠近單調性。
分類討論,
Case1:
若是 \(a_x\) 仍為最小,則希望單調遞減。則有柿子。
這個結論是 EI 所說過的。
Case2:
若是 \(a_x\) 不為最小,顯然需要 \(a_x>a_{n-1}-a_2\) 成立。又由於 \(a_x>a_2\) (不為最小),所以 \(a_x>a_{n-1}-a_2>a_{n-1}-a_x\) 結論同上。然后歸納出只要每次取出的最大值為最小值的兩倍以上就能保持單調。(此處有關等號的事情要求編號,無關緊要,手玩即可)
同時可以發現由於 \(a_x>a_2>a_1\) 所以 \(a_n-a_1>a_1\),\(Case2\) 恆滿足結論。
於是可知在 \(Case2\) “最大值減最小值不為新的最小值”(即 \(a_x>a_2\)),的情況下,可以使用雙隊列,符合單調性。,對於 \(Case1\) 則是只在滿足結論時單調。
更進一步
從現在開始,考慮的所有問題,都是回答當前最強蛇會不會吃。
考慮到在 \(case2\) 的情況下,這條蛇必吃無疑。
證明:因為單調性,下一條最大的蛇(\(a_{n-1}\)) 去吃最小值之后一定會小於 \(a_x\) 。所以就算要死,也是 \(a_{n-1}\) 先死,而所有的吃過的蛇都是不可能死的。不然它會不吃轉而直接結束游戲。所以 \(a_n\) 就不可能死。吃就完事了。
所以連續的 \(Case 2\) 直接模擬就行。於是會遇到第一個 \(Case1\)
單調性的消亡
很麻煩的是,\(Case1\) 時結論十分不好滿足。所以當成冒着“風險”去吃,更進一步的說,是先吃,然后觀察下一個最大的會不會吃。若是下一個不會吃,當前條就吃,否則不吃。此時需要注意到這個問題依舊沒有脫離“最強蛇會不會吃”的范疇,只是詢問的是下一條最強蛇,換句話說,有着遞歸性質。
那么遞歸的邊界有兩個。(即什么時候必吃)
第一個邊界是其后的某一條蛇遇見了 \(Case2\) ,必吃。第二個邊界是只剩兩條蛇了。
但是此時需要思考一個問題,即在 \(Case1\) 中,不滿足單調性,那么一直從兩個隊列中取出最大值和最小值還是對的嗎?答案是對的。而這也是我和機房同學思考了1個小時的問題。
單調性是相對於多次的插入同時都在隊列中而言的,但是可以發現,由於 \(Case1\) 的插入的 \(a_x\) 是最小的的性質,則必然會在下一次取出來。更具體的,當遇到一連串 \(Case1\) 的時候,就會一直在第二個隊列中的某個位置上反復存儲(若是中間有 \(Case2\) 就到達邊界了)。
同時最大值也肯定是對的,因為存儲的一直只與最小值有關,除非只剩兩條蛇,但這又恰好碰上了另一個邊界。
最大值合法,最小值也合法,不單調,也無妨。
奇偶性的影響
這個都比較簡單了,雖然說是遞歸,但是打的時候直覺就強烈的告訴我和奇偶性相關。
不妨考慮邊界做出決策的蛇是第 i 層 ,那么第 i-1 層必然不敢冒風險,於是第 i-2 層就能吃, i-3層不敢冒風險....
所以性質顯然,只需要記錄遞歸多少層就行。
CODE
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
using namespace std;
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
inline int read(){
char ch=getchar();
int res=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())res=(res<<3)+(res<<1)+(ch-'0');
return res*f;
}
const int MAXN=1e6+5,inf=1e9;
int t,n,a[MAXN];
pii q1[MAXN],q2[MAXN];//q1單調遞增,q2單調遞減
int l1,r1,l2,r2;//首尾指針
inline pii mx(){//取出最大值且彈出
if(r1==l1)return q2[l2++];
else if(r2==l2)return q1[--r1];
else if(q2[l2]>q1[r1-1])return q2[l2++];
else return q1[--r1];
}
inline pii mn(){//取出最小值並彈出
if(l1==r1)return q2[--r2];
else if(r2==l2)return q1[l1++];
else if(q2[r2-1]<q1[l1])return q2[--r2];
else return q1[l1++];
}
inline pii M_min(pii x,pii y){
return x<y?x:y;
}
inline void solve(){
l1=r1=l2=r2=0;
for(int i=1;i<=n;++i)q1[r1++]=mp(a[i],i);//初始化
int fl=0,cnt=0,alf=0;
while(1){
++cnt;//計數器,同時也是死掉的蛇數
pii x=mn(),y=mx();
pii z=M_min((l1<r1?q1[l1]:mp(inf,-inf)),(l2<r2?q2[r2-1]:mp(inf,-inf)));//再取出一個最小值
y.first-=x.first;
if(y>z||cnt==n-1){//Case2和邊界寫一起了
if(fl){
printf("%d\n",n-(fl-(alf&1)));//奇偶性
return ;
}
if(cnt==n-1){
printf("1\n");
return ;
}
q2[r2++]=y;//壓入隊列
}
else {
alf++;//記錄層數
if(!fl)fl=cnt; //第一個開始“冒風險”的蛇的之前死了多少
q2[r2++]=y;//壓入隊列
}
}
}
int main(){
t=read()-1;
n=read();
for(int i=1;i<=n;++i)a[i]=read();
solve();
while(t--){
int k=read();
for(int i=1,x;i<=k;++i)x=read(),a[x]=read();
solve();
}
return 0;
}