1. 程式人生 > >【BZOJ3895】取石子(博弈,記憶化搜索)

【BZOJ3895】取石子(博弈,記憶化搜索)

clu cst ring algorithm 一個 long 並且 details 記憶

題意:

Alice和Bob兩個好朋含友又開始玩取石子了。遊戲開始時,有N堆石子排成一排,然後他們輪流操作(Alice先手),
每次操作時從下面的規則中任選一個:
1:從某堆石子中取走一個
2:合並任意兩堆石子
不能操作的人輸。Alice想知道,她是否能有必勝策略
T<=100, N<=50. ai<=1000

思路:From https://blog.csdn.net/sunshinezff/article/details/50893626?utm_source=blogkpcl10

   考慮如果不存在石子數為1的堆。
設這種狀態下操作數為x,顯然x等於石子總數加操作數減1。
可以證明當x為奇數時先手必勝。當x為偶數時先手必敗。
如果只有1堆石子,該結論顯然成立。
如果有多堆石子,每堆石子個數都大於1,並且x為偶數,下面我們證明這樣先手必敗。
1.如果先手選擇合並兩堆石子,那麽每堆石子的個數依然大於1,x變為奇數。
2.如果先手選擇從一堆石子數大於2的堆中拿走一枚石子,那麽同上每堆石子個數依然大於1,x變為奇數。
3.如果先手選擇從一堆石子數等於2的堆中拿走一枚石子,那麽後手可以合並剩下的1枚石子到任意一個堆。
那樣x的奇偶性不變,每堆石子的個數依然大於1.
綜上所述,結論成立。
然後考慮如果存在石子數為1的堆.我們設石子數為1的堆的個數為x,其余堆的個數為y.
令f[x][y]為這種狀態下先手必勝或是必敗。
每次轉移枚舉所有可能的操作。記憶化搜索即可。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cmath>
 6 typedef long long ll;
 7 using namespace std;
 8 #define N   210000
 9 #define oo  10000000
10 #define MOD 1000000007
11 
12 int f[60][51000];
13  
14 int dfs(int a,int b)
15 {
16     if
(b==1) return dfs(a+1,0); 17 if(a==0) return b&1; 18 if(f[a][b]!=-1) return f[a][b]; 19 if(a&&!dfs(a-1,b)) return f[a][b]=1; //拿1 20 if(a&&b&&!dfs(a-1,b+1)) return f[a][b]=1; //將1和大於1的合並 21 if(a>=2&&!dfs(a-2,b+2+(b?1:0))) return f[a][b]=1; //合並2堆1 22 if
(b&&!dfs(a,b-1)) return f[a][b]=1; //合並2堆大於1的或拿1個大於1的 23 return f[a][b]=0; 24 } 25 26 int main() 27 { 28 memset(f,-1,sizeof(f)); 29 int cas; 30 scanf("%d",&cas); 31 while(cas--) 32 { 33 int n; 34 scanf("%d",&n); 35 int a=0; 36 int b=-1; 37 for(int i=1;i<=n;i++) 38 { 39 int x; 40 scanf("%d",&x); 41 if(x==1) a++; 42 else b+=x+1; 43 } 44 b=max(b,0); 45 if(dfs(a,b)) printf("YES\n"); 46 else printf("NO\n"); 47 } 48 return 0; 49 } 50

【BZOJ3895】取石子(博弈,記憶化搜索)