[除草]BZOJ 1413 [ZJOI2009]取石子游戏

题目大意:

从左到右N堆石子, 每对石子数为a[i]. 两方轮流操作, 一次操作可以从最左边或最右边的石子堆取任意数量石子, 但不能不取, 不能操作的输. 问先手是否有必胜策略. (N不超过1000)

 

简要分析:

好囧的博弈题...现在我的脑袋里还是一团乱麻...

在尝试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.

反正这个分析无比蛋疼...首先状态的定义非常奇葩, 具有一定的启发性(因为这题灰常隐蔽的一个性质)...然后分情况讨论无比痛苦...考场上还是找规律吧...

分类讨论的核心在于要找出后手的必胜策略.

 

代码实现:

View Code
 1 #include <cstdio>

 2 

 3 const int kMaxN = 1000;  4 int t, n, s[kMaxN], fl[kMaxN][kMaxN], fr[kMaxN][kMaxN];  5 

 6 int main() {  7     scanf("%d", &t);  8     while (t --) {  9         scanf("%d", &n); 10         for (int i = 0; i < n; ++ i) scanf("%d", &s[i]); 11         for (int i = 0; i < n; ++ i) fl[i][i] = fr[i][i] = s[i]; 12         for (int k = 1; k < n; ++ k) 13             for (int i = 0; i < n - k; ++ i) { 14                 int j = i + k, p, q, x; 15                 

16                 p = fl[i][j - 1], q = fr[i][j - 1], x = s[j]; 17                 if (x == q) fl[i][j] = 0; 18                 else if ((x < p && x < q) || (x > p && x > q)) fl[i][j] = x; 19                 else if (p < q) fl[i][j] = x + 1; 20                 else fl[i][j] = x - 1; 21 

22                 p = fr[i + 1][j], q = fl[i + 1][j], x = s[i]; 23                 if (x == q) fr[i][j] = 0; 24                 else if ((x < p && x < q) || (x > p && x > q)) fr[i][j] = x; 25                 else if (p < q) fr[i][j] = x + 1; 26                 else fr[i][j] = x - 1; 27  } 28         if (n == 1) printf("1\n"); 29         else printf("%d\n", (fr[0][n - 2] != s[n - 1])); 30  } 31     return 0; 32 }

你可能感兴趣的:(413)