【BZOJ1413】取石子游戲(博弈,區間DP)
題意:在研究過Nim遊戲及各種變種之後,Orez又發現了一種全新的取石子游戲,這個遊戲是這樣的:
有n堆石子,將這n堆石子擺成一排。遊戲由兩個人進行,兩人輪流操作,每次操作者都可以從最左或最右的一堆中取出若干顆石子,
可以將那一堆全部取掉,但不能不取,不能操作的人就輸了。
Orez問:對於任意給出一個初始一個局面,是否存在先手必勝策略。
T≤10 n≤1000 每堆的石子數目≤1e9
思路:From http://www.cnblogs.com/zcwwzdjn/archive/2012/05/26/2519685.html
在嘗試SG函式, 區間DP無果後, 在Discuss的誘導下注意到對於一段區間[L, R], 若L+1到R的石子數固定, 那麼使得在這段區間上先手必敗的a[L]有且僅有一個.
這個性質灰常給力啊. 於是可以YY一個狀態出來. 設left[i][j]表示, 在[i, j]區間的左邊加上left[i][j]這個數後先手必敗, right[i][j]的定義類似. 那麼最後我們只用看left[2][n]是否等於a[1]就可以了.
接著我們想辦法來算left[i][j]. right[i][j]可以類似的求出.
設L = left[i][j - 1], R = right[i][j - 1], X = a[j]. 通過下面的分析我們可以發現left[i][j]只和L, R, X三個數有關.
首先, 最容易想到的是, R = X的情況, 這時[i, j]這段區間已經先手必敗, 那麼left[i][j] = 0.
接著, 我們可以發選當X < L且X < R時, left[i][j] = X. 在這種局面下, 若先手在一側取走一些石子, 那麼後手在另外一邊取走相同數量的石子就可以了.
然後我們根據L和R的關係分類討論一下.
若L > R, 我們考慮R < x <= L的情況, 這時left[i][j] = X - 1. X - 1 = R時是很輕鬆的, 因為先手不能把右側石堆取到R, 所以後手保證每次取之後兩堆石子相同就可以了. 當X - 1 > R時, 若先手把左邊取到R, 那麼後手把右邊取到R + 1就可以了; 若先手取到R + 1, 那麼後手取到R + 2; 以此類推.
若L < R, 我們考慮L <= X < R的情況, 這時left[i][j] = X + 1. 這個和上面類似.
最後的一種情況, x > L且x > R. 其實left[i][j] = X. 若L = R, 沒啥說的; 若L和R不等, 我們不妨設L > R, 這時若先手把右邊取到L, 那麼後手需要把左邊取到L - 1, 這時如果先手跟著後手走, 那麼後手一顆一顆石子取就贏了; 若先手把左邊取到R, 那麼後手需要把右邊取到R + 1, 這種情況似乎一定成立, 因為後手不會主動走到R + 1, 除非對方走到了R.
反正這個分析無比蛋疼...首先狀態的定義非常奇葩, 具有一定的啟發性(因為這題灰常隱蔽的一個性質)...然後分情況討論無比痛苦...考場上還是找規律吧...
分類討論的核心在於要找出後手的必勝策略.
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 1100 9 #define oo 10000000 10 #define MOD 1000000007 11 12 int l[N][N],r[N][N],a[N]; 13 14 int main() 15 { 16 int cas; 17 scanf("%d",&cas); 18 while(cas--) 19 { 20 int n; 21 scanf("%d",&n); 22 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 23 for(int i=1;i<=n;i++) l[i][i]=r[i][i]=a[i]; 24 for(int len=2;len<=n;len++) 25 for(int i=1;i<=n-len+1;i++) 26 { 27 int j=i+len-1; 28 int p=l[i][j-1]; 29 int q=r[i][j-1]; 30 int x=a[j]; 31 if(x==q) l[i][j]=0; 32 else if((x<p&&x<q)||(x>p&&x>q)) l[i][j]=x; 33 else if(p<q) l[i][j]=x+1; 34 else l[i][j]=x-1; 35 p=r[i+1][j]; 36 q=l[i+1][j]; 37 x=a[i]; 38 if(x==q) r[i][j]=0; 39 else if((x<p&&x<q)||(x>p&&x>q)) r[i][j]=x; 40 else if(p<q) r[i][j]=x+1; 41 else r[i][j]=x-1; 42 } 43 if(n==1) printf("1\n"); 44 else printf("%d\n",(r[1][n-1]!=a[n])); 45 } 46 return 0; 47 } 48