1. 程式人生 > >【BZOJ1413】取石子游戲(博弈,區間DP)

【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