7.3 KiB
一、题目描述
在研究过 Nim
游戏及各种变种之后,Orez
又发现了一种全新的取石子游戏,这个游戏是这样的:
有 n
堆石子,将这 n
堆石子摆成一排。
游戏由两个人进行,两人轮流操作,每次操作者都可以从 最左 或 最右 的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。
Orez
问:对于任意给出的一个初始局面,是否存在先手必胜策略。
输入格式
第一行为一个整数 T
,表示有 T
组测试数据。
对于每组测试数据,第一行为一个整数 n
,表示有 n
堆石子,第二行为 n
个整数 a_i
,依次表示每堆石子的数目。
输出格式
对于每组测试数据仅输出一个整数 0
或 1
,占一行。
其中 1
表示有先手必胜策略,0
表示没有。
数据范围
1≤T≤10,1≤n≤1000,1≤a_i≤10^9
输入样例:
1
4
3 1 9 4
输出样例:
0
二、思考过程
1、状态定义
① 设 left[i][j]
表示在 [i,j]
已经固定的区间 左侧 放上一堆数量为 left[i][j]
的石子后,先手必败
② 设 right[i][j]
表示在 [i,j]
已经固定的区间 右侧 放上一堆数量为 right[i][j]
的石子后,先手必败
即:(left[i][j],\underbrace{a_i,a_{i+1},\cdots,a_j}_{a[i]\sim a[j]})
,(\underbrace{a_i,a_{i+1},\cdots,a_j}_{a[i]\sim a[j]},right[i][j])
为 先手必败 局面
有如下两个性质:
2、left[i][j]
,right[i][j]
一定存在
反证法:
假设不存在满足定义的 left[i][j]
,则对于 任意非负整数 x
,有形如:
\large \underbrace{x,a_i,a_{i+1},\cdots,a_j}_{A(x)}
由于 A(x)
为必胜局面,故从 A(x)
局面 必然存在若干种办法一步可达必败局面。
若从最左边一堆中拿,因为假设原因,不可能变成必败局面,因为这样得到的局面仍形如 A(x)
。
左边拿没用,只能考虑从右边拿(即从a_j
里拿):
于是设 A(x)
一步可达的某个 必败局面为 (x,a_i,a_{i+1},\cdots,a_{j-1},y)
,显然有 0 \le y < a_j
。
由于 x
有无限个,但 y
只有 a_j
种——根据抽屉原理,必然存在 x_1,x_2(x_1 > x_2),y
满足 (x_1,a_i,a_{i+1},\cdots,a_{j-1},y)
和 (x_2,a_i,a_{i+1},\cdots,a_{j-1},y)
都是必败局面。但这两个必败局面之间 实际一步可达(比如拿走x_1-x_2
个),矛盾,假设不成立,原命题成立。
3、left[i][j]
,right[i][j]
必然唯一
反证法:
假设 left(i,j)
不唯一,则存在非负整数 x_1,x_2(x_1 \neq x_2)
,使得(x_1,a_i,a_{i+1},⋯,a_{j−1},a_j)
和 (x_2,a_i,a_{i+1},\cdots,a_{j-1},a_j)
均为必败局面,而 第一个必败局面 可以通过拿走左侧x_1-x_2
个石子到达另一个 必败局面 ,矛盾,假设不成立,原命题成立。
4、推论
有了上面推的left[i][j]
唯一性,得出一个有用的推论:
对于任意非负整数 x \neq left(i,j)
,\large (x,a_i,a_{i+1},\cdots,a_j)
为必胜局面
5、定义L
和R
因为下面的讨论中出现的L
和R
的含义不是很好理解,我们先把这个概念理清楚:
Q
:为什么要这么定义L
和R
呢?
答:博弈论的题目都是可以通过动态规划来递推的。
为什么呢?你想啊,最终的胜利,肯定不是从天下直接掉下来的,是一步步取得的,也就是前序状态会直接影响后面的状态,所以
博弈论
\Leftrightarrow
动态规划\Leftrightarrow
递归
递推嘛,就是类似于 数学归纳法,先求出初始状态是多少,然后假设i \sim j-1
这段已经计算出left[i][j-1],right[i][j-1]
了,现在想依赖于这两个数值推导出left[i][j],right[i][j]
,怕写的太长麻烦,就定义了L=left[i][j-1],R=right[i][j-1]
left[i][j]
递推
?[i j-1] 第j堆(石子个数x)
则我们希望求的是假设i\sim j
已经固定了,我们在左边放多少个可以使得?[i j-1] 第j堆
是必败的
定义:
左边放L
时,L[i j-1]
必败
右边放R
时,[i j-1]R
必败
Q
:那为什么是定义先手必败,而不是先手必胜呢?
答:因为上面证明过定义先手必败的动态规划数组,是肯定存在并且是唯一的,这样才能正常计算啊。
考虑三个问题:
- ① 初始值
- ② 答案在哪
- ③ 递推式
注:答案在哪,并不是和递推式相关,而是和状态表示相关,一定要注意
① 初始值
\large L[i][i]=R[i][i]=a_i
当只有一堆石子时(only
i
),我在这堆前面添加一堆,个数和这堆一样多,对于两堆相同的石子,后手进行和先手对称的操作,你咋干我就咋干,我拿完,你瞪眼~, 先手必败
② 答案在哪
先手必败 \Leftrightarrow \ L[2][n]=a_1
解释:如果
L[2][n]
与a[1]
相等,就意味着本来挺好的a[2] \sim a[n]
,结果,前面放上了一个a[1]
,而加上的a[1]
使得现在的局面必败,先手必败。
③ 递推式
六、实现代码
#include <cstdio>
using namespace std;
const int N = 1010;
int n;
int a[N], l[N][N], r[N][N];
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int len = 1; len <= n; len++)
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
if (len == 1)
l[i][j] = r[i][j] = a[i];
else {
int L = l[i][j - 1], R = r[i][j - 1], x = a[j];
if (R == x)
l[i][j] = 0;
else if (x < L && x < R || x > L && x > R)
l[i][j] = x;
else if (L > R)
l[i][j] = x - 1;
else
l[i][j] = x + 1;
// 与上述情况对称的四种情况
L = l[i + 1][j], R = r[i + 1][j], x = a[i];
if (L == x)
r[i][j] = 0;
else if (x < L && x < R || x > L && x > R)
r[i][j] = x;
else if (R > L)
r[i][j] = x - 1;
else
r[i][j] = x + 1;
}
}
if (n == 1)
puts("1");
else
printf("%d\n", l[2][n] != a[1]);
}
return 0;
}