1. 程式人生 > 其它 >[博弈論]取石子游戲

[博弈論]取石子游戲

在研究過 Nim 遊戲及各種變種之後,Orez 又發現了一種全新的取石子游戲,這個遊戲是這樣的:
有 n 堆石子,將這 n 堆石子擺成一排。
遊戲由兩個人進行,兩人輪流操作,每次操作者都可以從最左或最右的一堆中取出若干顆石子,可以將那一堆全部取掉,但不能不取,不能操作的人就輸了。
Orez 問:對於任意給出的一個初始局面,是否存在先手必勝策略。

輸入格式
第一行為一個整數 T,表示有 T 組測試資料。
對於每組測試資料,第一行為一個整數 n,表示有 n 堆石子,第二行為 n 個整數 ai ,依次表示每堆石子的數目。

輸出格式
對於每組測試資料僅輸出一個整數 0 或 1,佔一行。
其中 1 表示有先手必勝策略,0 表示沒有。

思路:
太難想了
定義 l [ i , j ] 代表 在第 [ i , j ] 堆石子的左邊放多少石子,先手必敗
定義 r [ i , j ] 代表 在第 [ i , j ] 堆石子的右邊放多少石子,先手必敗
因為這兩者對稱,所以我們先只討論 l [ i , j ] 的情況
考慮 l [ i , j ]
一定存在嗎? 存在,每一步都可以通過遞推推出來。
唯一嗎?
唯一,反證法:
假設有兩個l [ i , j ] 都使先手必敗,大的那個轉移到小的那個時,必敗 ->必敗
衝突,大的那個轉移成小的那個,則大的那個為必勝態。
所以一定唯一。

假設第i堆到第j堆已經固定了。
? [ i , j - 1] X
X 代表第 j 堆 有幾個石子


? 即 l [ i , j ]。
定義
在 左邊放 L 時, L [ i , j - 1] 必敗 這裡的 L 其實就 = l [ i , j -1]
在 右邊放 R 時,[ i , j - 1] R 必敗 這裡的 R 其實就 = r[ i , j - 1]

分類討論:
如果 X 正好等於 R,則本身就已經必敗了,在左邊放0個就好了
如果 X < L && X < R, 則 在左邊也放 X 個,兩邊都是X個,先手拿多少,後手在另一邊拿多少,所以肯定是後手拿最後一次。當後手拿最後一次時,另一邊肯定已經是0了,而不管最後剩下的是多少,留給後手的局面肯定不是L,也不是R,因為X比這兩個都小。留給後手的是不是必敗,所以先手必敗。
如果

L > R,推出 R < X <= L ,則在最左邊放 X - 1 個石子的時候先手必敗。
當先手拿右邊時

  • 首先,先手一定不能把右邊取到R
    若先手把右邊取到R,後手把左邊取完。則先手必敗

  • 如果先手把右邊取到x<R時, 後手立即把左邊取到和右邊相同
    –轉化為情況②–先手必敗

  • 如果先手把右邊取到x>R時,後手立即把左邊取到x−1
    由於右邊 >R 則右邊x最小取到1 左邊x最小取到0
    則必然會將右邊取到R(情況①)或者R以下(情況②) – 必敗

當先手拿左邊時

  • 當先手把左邊取到>=R,後手就把右邊取到比左邊多1保持情況③

  • 當先手把左邊取到<R,後手就右邊取到和左邊相等 保持情況②

如果L < R,推出L<=X<R,則在左邊放X+1個石子時,先手必敗
當先手拿左邊時,

  • 如果先手把左邊取到>=L+1,則後手就把右邊取到>=L,保持情況④

  • 如果先手把左邊取到L,則後手把右邊取到0

  • 如果先手把左邊取到<L,則後手保持左右兩邊相等,情況②

當先手拿右邊時,

  • 當先手把右邊取到>=L時,則後手把左邊取到比右邊多1 保持情況④

  • 當先手把右邊取到<L時,則後手保持兩邊相等,情況②

⑤ 如果 X > R && X > L 時,左邊放X個和右邊一樣多,先手必敗
L>R時
先手取完後 >L 後手保證左右兩邊相同

一旦先手把某一邊個數取到(R,L]後手保證左邊比右邊少一個(情況③)
一旦先手把某一邊個數取到[R,L) 後手保證右邊比左邊多一個
一旦先手把某一邊個數取到(,R) 後手保證右邊和左邊一樣多

R>L時
對稱
先手取完後>R 後手保證左右兩邊相同
一旦先手把某一邊個數取到(L,R]後手保證右邊比左邊少一個(情況④)
一旦先手把某一邊個數取到[L,R)後手保證左邊比右邊多一個
一旦先手把某一邊個數取到(,L)後手保證右邊和左邊一樣多

區間dp,先列舉長度,後列舉左右端點,大範圍依賴小範圍,
當長度為1時,l[i][j]=r[i][j]=a[i]==a[j] 在左邊放一樣多就可以使先手必敗

const int N = 1010;
int l[N][N],r[N][N];
int t,n;
int a[N];
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int len = 1;len <= n;len ++){
            for(int i=1;i+len-1<=n;i++){
                int j = i + len - 1;
                int L = l[i][j-1], R = r[i][j-1], X = a[j];
                if(len == 1) l[i][j] = r[i][j] = a[i];
                else{
                    if(X == R) l[i][j] = 0;//①
                    //②⑤都是X,所以一起寫
                    else if(X < L && X < R || X > L && X > R) l[i][j] = X;
                    else if(L > R) l[i][j] = X-1;//③
                    else l[i][j] = X+1;//④
                }
                L = l[i+1][j],R = r[i+1][j],X = a[i];
                if(len == 1) l[i][j] = r[i][j] = a[j];
                else{
                    if(X == L) r[i][j] = 0;
                    else if(X < L && X < R || X > L && X > R) r[i][j] = X;
                    else if(L < R) r[i][j] = X-1;
                    else r[i][j] = X+1;
                }
            }
        }
        printf("%d\n",l[2][n] != a[1]);
    }
    
    return 0;
}