題解 題解 P3210 【[HNOI2010]取石頭遊戲】
阿新 • • 發佈:2020-07-14
考慮到先手和後手都使用最優策略,所以可以像對抗搜尋一樣,設 \(val\) 為先手收益減去後手收益的值。那麼先手想讓 \(val\) 儘可能大,後手想讓 \(val\) 儘可能小。
繼續分析題目性質,發現取石子的過程可以轉化為兩端分別有一個棧,可以從棧頂取石子,中間有若干個雙端佇列,可以從其兩端取石子。
如果取一個位置後,接下來的位置比剛才取的那個位置權值小,也就是從選擇方向開始權值是遞減的,每次決策肯定都是取當前局面權值最大的位置。如果不保證遞減,就有可能取完一個位置後,使得一個權值更大的位置可以取,這時按最大值決策就有可能不是最優。
對於 \(a_{i-1},a_i,a_{i+1}\),若其滿足 \(a_i \geqslant a_{i-1},a_i \geqslant a_{i+1}\)
合併完後所有的權值情況只存在遞增,遞減和下凹的情況了,這三個情況對於雙端佇列都是可以單調的從大到小選,對於從棧頂方向開始遞減的棧的部分位置,也是可以單調的從大到小選,這些位置就可以直接排序後先後手一個一個選了。
而對於從棧頂方向開始遞增的棧的部分位置,一定是最後才開始選的,因為這些決策一定是劣於其他決策,提前處理出其選擇的結果,最後根據先後手的情況分配即可。
合併刪除操作可以用連結串列來實現。
\(code:\)
#include<bits/stdc++.h> #define maxn 2000010 using namespace std; typedef long long ll; template<typename T> inline void read(T &x) { x=0;char c=getchar();bool flag=false; while(!isdigit(c)){if(c=='-')flag=true;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} if(flag)x=-x; } ll n,sum,val,s,L,R,tot; ll l[maxn],r[maxn],v[maxn]; bool tag[maxn]; bool cmp(const ll &a,const ll &b) { return a>b; } int main() { read(n),r[0]=1,l[n+1]=n; for(int i=1;i<=n;++i) read(v[i]),sum+=v[i],l[i]=i-1,r[i]=i+1,tag[i]=(v[i]!=0); for(int i=3;i<=n;i=r[i]) while(tag[l[l[i]]]&&tag[l[i]]&&tag[i]&&v[l[i]]>=v[l[l[i]]]&&v[l[i]]>=v[i]) v[i]=v[l[l[i]]]+v[i]-v[l[i]],r[l[l[l[i]]]]=i,l[i]=l[l[l[i]]]; L=r[0],R=l[n+1]; while(v[L]>=v[r[L]]&&tag[L]&&tag[r[L]]) s+=v[r[L]]-v[L],L=r[r[L]]; while(v[R]>=v[l[R]]&&tag[R]&&tag[l[R]]) s+=v[l[R]]-v[R],R=l[l[R]]; for(int i=L;i<=R;i=r[i]) if(tag[i]) v[++tot]=v[i]; sort(v+1,v+tot+1,cmp),v[++tot]=s; for(int i=1;i<=tot;++i) { if(i&1) val+=v[i]; else val-=v[i]; } printf("%lld %lld",(sum+val)/2,(sum-val)/2); return 0; }