You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1322. 取石子游戏

一、题目描述

在研究过 Nim 游戏及各种变种之后,Orez 又发现了一种全新的取石子游戏,这个游戏是这样的:

n 堆石子,将这 n 堆石子摆成一排。

游戏由两个人进行,两人轮流操作,每次操作者都可以从 最左最右 的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了

Orez 问:对于任意给出的一个初始局面,是否存在先手必胜策略。

输入格式 第一行为一个整数 T,表示有 T 组测试数据。

对于每组测试数据,第一行为一个整数 n,表示有 n 堆石子,第二行为 n 个整数 a_i ,依次表示每堆石子的数目。

输出格式 对于每组测试数据仅输出一个整数 01,占一行。

其中 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_{j1},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=0L=R=0,此时 x>\max\{L,R\},也就是说 L=0R=0 都属于 Case 5,故其它 Case 满足 L,R>0

注:因R=0,表示在[i,j-1]确定后,右侧为0就能满足[i,j-1]这一段为先手必败,此时,左侧增加那堆个数为0就可以继续保持原来的先手必败,即L=0

5、分类讨论

  • x=RCase 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_{j1})(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 < RCase 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 LCase 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,将另一堆拿成 LR 个石子即可得到必败局面。

        • 剩余的情况是 L \le z \le RR \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;
}