13 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
二、状态定义
① 原来每堆数量是长成这个样的,可能是必胜状态,也可能是必败状态,都可以:
a_i |
a_{i+1} |
... | a_{j-1} |
a_{j} |
---|
- 设
left[i][j]
表示在 必胜区间[i,j]
区间的 左侧 放上一堆数量为left[i][j]
的石子后,先手必败 - 设
right[i][j]
表示在 必胜区间[i,j]
区间的 右侧 放上一堆数量为right[i][j]
的石子后,先手必败
② 假如原来a_i \sim a_j
为必胜态,那么你前面添上啥都是必败的
③ 假如原来a_i \sim a_j
为必败态,那么你前面添上left[i][j]=0
也还是必败的
总结:不管原来a_i \sim a_j
是啥状态,反正,都可以通过向左边添加一个堆的方法(堆的厂子数量可以为0
)使得状态改为 先手必败
left[i][j] |
a_i |
a_{i+1} |
... | a_{j-1} |
a_{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])
为 先手必败 局面
三、left[i][j]
的存在性证明
博弈论的题,时刻要记得
转化关系
必胜态 \rightarrow
\large \left\{\begin{matrix}
合适的办法 & \rightarrow & 必败态(让对手必败) \\
走错了(傻了) & \rightarrow & 必胜态(让对手必胜)
\end{matrix}\right.
必败态 \rightarrow
\large \left\{\begin{matrix}
无论怎么走(绝望) & \rightarrow & 必胜态(让对手必胜) \\
永远无法(绝望) & \rightarrow & 必败态(让对手必败)
\end{matrix}\right.
(right[i][j]
同理,下同):
反证法:
假设不存在满足定义的 left[i][j]
,则对于 任意非负整数 x
,有形如:
\large \underbrace{x,a_i,a_{i+1},\cdots,a_j}_{A(x)}
由于 A(x)
为必胜局面,故从 A(x)
局面 必然存在M
种一步可达必败局面。
若从最左边一堆中拿,因为假设原因,不可能变成必败局面,因为这样得到的局面仍形如 A(x)
。
注意包括此行在内的接下来几行默认 x \neq 0
左边拿没用,只能考虑从右边拿:
于是设 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 \neq 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)
都是必败局面。但这两个必败局面之间 实际一步可达,故矛盾,进而原命题成立。
四、left[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)
均为必败局面,而这两个必败局面之间 实际一步可达 ,故矛盾,进而原命题成立。
五、状态转移
1、边界情况
\LARGE left[i][i]=a_i
当只有一堆石子时,我在这堆前面添加一堆,个数和这堆一样多,对于两堆相同的石子,后手进行和先手对称的操作,你咋干我就咋干,我拿完,你瞪眼~, 先手必败
2、递推关系
- 变化方法:从左侧拿走一些石子或者从右侧拿走一些石子
- 让我们使用
left[i][j-1]
和right[i][j-1]
来表示left[i][j]
和right[i][j]
,形成DP
递推关系
前面动作都按要求整完了,问我们:本步骤,我们有哪些变化,根据这些变化,怎么样用前面动作积累下来的数据来完成本步骤数据变化的填充,这不就是动态规划吗?
3、推论
有了上面推的left[i][j]
唯一性,得出一个有用的推论:
对于任意非负整数 x \neq left(i,j)
,\large (x,a_i,a_{i+1},\cdots,a_j)
为必胜局面
4、特殊情况:L=R=0
为方便叙述,下文记 left[i][j-1]
为 L
,记 right[i][j-1]
为 R
,并令 \displaystyle \large x=a_j(x>0)
若 R=0
则 L=R=0
,此时 x>\max\{L,R\}
,也就是说 L=0
和 R=0
都属于 Case
5
,故其它 Case
满足 L,R>0
。
注:因R=0
,表示在[i
,j-1
]确定后,右侧为0
就能满足[i
,j-1
]这一段为先手必败,此时,左侧增加那堆个数为0
就可以继续保持原来的先手必败,即L=0
。
5、分类讨论
-
x=R
(Case
1
) 最简单的情况——根据R=right[i][j-1]
的定义,区间[i,j]
本来就是必败局面,因此左边啥也不能添,添了反而错,故\large left[i][j]=0
-
x<R
x<L
,即x< \min\{L,R\}
(Case
2
)- 结论:
\large left[i][j]=x
-
证明: 即 求证
\large (x,a_i,a_{i+1},\cdots,a_{j-1},x)
为必败局面,其中x< \min\{L,R\}
由于最左边和最右边的两堆石子数量相同,后手可进行和先手 对称 操作,后手必将获得一个形如
(y,a_i,a_{i+1},⋯,a_{j−1})
或(a_i,a_{i+1},\cdots,a_{j-1},y)
的局面,其中:0<y < x<\min\{L,R\}
只有左侧为
L=left(i,j-1)
这个唯一值时,才是必败态,现在不是L
,而是y<min(L,R)
,所以后手必胜,即先手必败, 证毕。
x \geq L
,即L \leq x < R
(Case
3
)-
结论:
\large left[i][j]=x+1$$
-
证明: 即 求证
(x+1,a_i,a_{i+1},\cdots,a_{j-1},x)
为 必败局面 ,其中L \leq x <R
- 若先手拿最左边一堆,设拿了以后 还剩
z
个石子- 若
z>L
,则后手将最右堆拿成z-1
个石子(z-1 \ge L>0
),保证左侧比右侧多1
个石子,就能回到Case
3
本身,递归证明即可 - 若
z=L
,则后手将最右堆拿完,根据L[i][j-1]
定义知此时局面必败 - 若
0<z<L
,则后手将最右堆拿成z
个石子,由Case
2
知此时是必败局面 - 若
z=0
,此时最右堆石子数x
满足L \le x<R
,结合right[i][j-1]
定义知 此局面必胜
- 若
- 若先手拿最右边一堆,设拿了以后 还剩
z
个石子- 若
z \ge L
,则后手将最左堆拿成z+1
个石子,就能回到Case
3
本身,递归证明即可 - 若
0<z<L
,则后手将最左堆拿成z
个石子,由Case
2
知此时是必败局面 - 若
z=0
,则后手将最左堆拿成L
个石子,由left[i][j-1]
定义知此时局面必败
- 若
- 若先手拿最左边一堆,设拿了以后 还剩
-
-
x>R
-
x≤L
,即R < x \leq L
(Case
4
)-
结论:
\large left[i][j]=x-1$$
-
证明:
- 若先手拿最左边一堆,设拿了以后还剩
z
个石子。- 若
z \geq R
,则后手将最右堆拿成z+1
个石子,保证左侧比右侧多1
个石子,就能回到Case
4
本身,递归证明即可。 - 若
0<z<R
,则后手将最右堆拿成z
个石子,由Case
2
知此时是必败局面。 - 若
z=0
,则后手将最右堆拿成R
个石子(注意Case
4
保证了此时最右堆石子个数>R
),由right[i][j-1])
的定义知此时是必败局面。
- 若
- 若先手拿最右边一堆,设拿了以后还剩
z
个石子。- 若
z>R
,则后手将最左边一堆拿成z-1
个石子(注意z-1 \ge R >0
),递归证明即可。保证右侧比左侧多1
个石子。 - 若
z=R
,则后手把最左堆拿完,根据right[i][j-1]
的定义可知得到了必败局面。 - 若
0<z<R
,则后手将最左堆拿成z
个石子,由Case
2
知此时是必败局面。 - 若
z=0
,此时最左堆石子数量k
满足0<k<L
,结合left[i][j-1]
定义知局面必胜
- 若
- 若先手拿最左边一堆,设拿了以后还剩
-
-
x>L
,即x>\max\{L,R\}
(Case
5
)-
结论:
\large left[i][j]=x$$
-
证明: 设先手将其中一堆拿成了
z
个石子。-
若
z>\max\{L,R\}
,后手将另一堆也拿成z
个,回到Case
5
,递归证明。 -
若
0<z<\min\{L,R\}
,后手把另一堆也拿成z
个石子即可转Case
2
。 -
若
z=0
,将另一堆拿成L
或R
个石子即可得到必败局面。 -
剩余的情况是
L \le z \le R
或R \le z \le L
。 Case
3
可以解决最左堆L +1 \le z \le R
,最右堆L \le z \le R-1
的情况 Case
4
可以解决最左堆R \le z \le L-1
,最右堆R+1 \le z \le L
的情 况。
所以只需解决最左堆
z=L
和最右堆z=R
的情况。而这两种情况直接把另一堆拿完就可以得到必败局面。 -
-
-
综上所述:
\large
L[i][j]=
\large \left\{\begin{matrix}
0 & x=R \\
x+1&L \leq x < R \\
x-1 & R<x \leq L \\
x & otherwise
\end{matrix}\right.
温馨提示:请看清楚 L
取不取等,乱取等是错的!
同理可求 R(i,j)
。
回到原题,先手必败当且仅当 L[2][n]=a_1
,于是我们就做完啦!
时间复杂度 O(n^2)
。
六、实现代码
#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;
}