[博弈論]取石子游戲
在研究過 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比這兩個都小。留給後手的是不是必敗,所以先手必敗。
③ 如果
當先手拿右邊時,
-
首先,先手一定不能把右邊取到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;
}