目录
题目-取石子游戏

问题分析

用 l ( i , j ) l(i, j) l(i,j)表示对于当前情况, 左边放多少个石子先手必败
必然存在 并且必然唯一 , 第一个可以用递推的方式计算出, 对于唯一性, 以下是证明过程
证明:

假设对于某种局面有两个取值 L 1 L_1 L1, L 2 L_2 L2, 不妨设 L 1 < L 2 L_1 < L_2 L1<L2, 假设左边放了 L 1 L_1 L1个, 那么当前状态就是必败态
拿走 L 1 − L 2 L_1 - L_2 L1−L2个石子, 剩余左边的式子个数是 L 2 L_2 L2, 还是必败态
也就是说必败态转移到了必败态 , 矛盾
因此不存在 L 1 < L 2 L_1 < L_2 L1<L2的情况 , 同理不存在 L 1 > L 2 L_1 > L_2 L1>L2的情况
因此 L L L的取值只有一种, 证毕
同理给出 r ( i , j ) r(i, j) r(i,j), 假设假设计算出了 l ( i , j ) l(i, j) l(i,j)的所有取值
那么当 a 1 = l ( 2 , n ) a_1= l(2, n) a1=l(2,n)的时候先手必败
设 x = a j x = a_j x=aj具体来说如何计算 l ( i , j ) l(i, j) l(i,j)和 r ( i , j ) r(i, j) r(i,j), 因为 l l l和 r r r是对称的, 先考虑 l ( i , j ) l(i, j) l(i,j)如何计算

给出如下定义
- 定义 L L L为左边放多少加上 [ i , j − 1 ] [i, j - 1] [i,j−1]是必败态
- 定义 R R R为右边放多少加上 [ i , j − 1 ] [i, j - 1] [i,j−1]是必败态
分情况讨论
情况一
x = R x = R x=R, 那么当前已经是必败态了
l ( i , j ) = 0 l(i, j) = 0 l(i,j)=0
整体就是必败态
情况二

x < L , x < R x < L, x < R x<L,x<R l ( i , j ) = x l(i, j) = x l(i,j)=x
先手必败
策略: 后手拿取相同的个数 , 因为后手取完之后给先手的情况左右两边是相同的 , 并且石子个数是有限的, 必然会先手取完某一边变为 0 0 0个石子 , 另外一边还有石子个数是 y y y个
因为 x < L , x < R x < L, x < R x<L,x<R, 因此 y < L , y < R y < L, y < R y<L,y<R
因为必败态是唯一的, 要么左边添加 L L L个石子右边剩余 0 0 0个石子, 要么右边添加 R R R个石子左边剩余 0 0 0个石子
但是当前剩余的石子个数是 y y y, 因此左边是 y y y或者右边是 y y y都不是必败态 , 也就是留给对手的局面一定是必胜态, 证毕
情况三
L > R L > R L>R, 也就是 R < x < L R < x < L R<x<L
左边取 x − 1 x - 1 x−1, 也就是
l ( i , j ) = x − 1 l(i, j) = x - 1 l(i,j)=x−1
先手必败
首先先手不可能将右侧的 R R R拿完, 拿完就是必败态
假设先手拿左边的一堆, 左边剩余了 z z z个石子

如果 z ≥ R z \ge R z≥R, 后手将最右堆拿成 z + 1 z + 1 z+1状态(为了保证 R ′ R' R′始终小于 L ′ L' L′), 还是当前状态, 递归处理
直到 z < R z < R z<R也就是 z < L z < L z<L, 变成了情况二
假设先手拿右边的一堆, 右边剩余了 z z z个石子
如果 z ≥ R z \ge R z≥R, 左边拿 z − 1 z - 1 z−1个石子, 回到了当前情况, 递归处理
直到 z < R z < R z<R, 回到情况二
情况四
L < R L < R L<R, 也就是 L < x ≤ R L < x \le R L<x≤R
左边取 x + 1 x + 1 x+1, 也就是
l ( i , j ) = x + 1 l(i, j) = x + 1 l(i,j)=x+1
先手必败
证明与情况三类似, 略
情况五
x > L , x > R x > L, x > R x>L,x>R
左边取 x x x, 也就是
l ( i , j ) = x l(i, j) = x l(i,j)=x
先手必败
设先手将其中一堆拿成了 z z z个石子情况, 假设 z ≥ max ( L , R ) z \ge \max(L, R) z≥max(L,R), 就是当前情况, 递归处理
直到 z < min ( L , R ) z < \min(L, R) z<min(L,R), 是情况二, l ( i , j ) = x l(i, j) = x l(i,j)=x
算法步骤
计算 l ( i , j ) l(i, j) l(i,j)和 r ( i , j ) r(i, j) r(i,j)
因为当前状态依赖前一段 状态, 因此可以区间 d p dp dp解决
算法时间复杂度 O ( n 2 ) O(n ^ 2) O(n2)
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N];
int 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;
}