1. 程式人生 > 實用技巧 >題解 題解 P3210 【[HNOI2010]取石頭遊戲】

題解 題解 P3210 【[HNOI2010]取石頭遊戲】

考慮到先手和後手都使用最優策略,所以可以像對抗搜尋一樣,設 \(val\) 為先手收益減去後手收益的值。那麼先手想讓 \(val\) 儘可能大,後手想讓 \(val\) 儘可能小。

繼續分析題目性質,發現取石子的過程可以轉化為兩端分別有一個棧,可以從棧頂取石子,中間有若干個雙端佇列,可以從其兩端取石子。

如果取一個位置後,接下來的位置比剛才取的那個位置權值小,也就是從選擇方向開始權值是遞減的,每次決策肯定都是取當前局面權值最大的位置。如果不保證遞減,就有可能取完一個位置後,使得一個權值更大的位置可以取,這時按最大值決策就有可能不是最優。

對於 \(a_{i-1},a_i,a_{i+1}\),若其滿足 \(a_i \geqslant a_{i-1},a_i \geqslant a_{i+1}\)

,當一次決策選 \(a_{i-1}\) 最優時,先手選 \(a_{i-1}\),其後手一定會接著選 \(a_i\),然後先手會接著選 \(a_{i+1}\)。選 \(a_{i-1}\) 時,當前局面一定沒有比 \(a_{i-1}\) 更好的選擇,而 \(a_i\)\(a_{i-1}\) 更優,所以後手一定選 \(a_i\),因為之前 \(a_{i-1}\) 是最優的選擇,所以先手會接著選 \(a_{i+1}\)。因此把 \(a_{i-1},a_i,a_{i+1}\)\(val\) 的貢獻看作整體,將其合併為一個權值為 \(a_{i-1}+a_{i+1}-a_i\) 的石子。

合併完後所有的權值情況只存在遞增,遞減和下凹的情況了,這三個情況對於雙端佇列都是可以單調的從大到小選,對於從棧頂方向開始遞減的棧的部分位置,也是可以單調的從大到小選,這些位置就可以直接排序後先後手一個一個選了。

而對於從棧頂方向開始遞增的棧的部分位置,一定是最後才開始選的,因為這些決策一定是劣於其他決策,提前處理出其選擇的結果,最後根據先後手的情況分配即可。

合併刪除操作可以用連結串列來實現。

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