[除草]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.
反正这个分析无比蛋疼...首先状态的定义非常奇葩, 具有一定的启发性(因为这题灰常隐蔽的一个性质)...然后分情况讨论无比痛苦...考场上还是找规律吧...
分类讨论的核心在于要找出后手的必胜策略.
代码实现:
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 }