AcWing 1322. 取石子游戏

题目传送门

参考题解

一、状态定义

\(L[i][j]\) 表示在 \([i,j]\) 区间的左侧放上一堆数量为 \(L[i][j]\) 的石子后,先手必败

\(L[i][j]\)可以为\(0\),此时\(a_i \sim a_j\)就已经是必败态了,前面什么也不用加。

\(L[i][j]\) \(a_i\) \(a_{i+1}\) ... \(a_{j-1}\) \(a_{j}\) \(R[i][j]\)

即:\((L[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]},R[i][j])\)先手必败局面。


二、\(L[i][j]\)存在性证明

\(R[i][j]\) 同理,下同):

反证法:
假设不存在满足定义的 \(L[i][j]\),则对于任意非负整数 \(x\),有形如:

\[\large \underbrace{x,a_i,a_{i+1},\cdots,a_j}_{A(x)} \]

都为必胜局面,记为 \(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)\) 都是必败局面。但这两个必败局面之间实际一步可达,故矛盾,进而原命题成立。

三、\(L[i][j]\) 的唯一性证明

反证法:
假设 \(L(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 L[i][i]=a_i \]

对于两堆相同的石子后手进行和先手对称的操作,你咋干我就咋干,我拿完,你瞪眼~

2、场景分析

  • 边界情况:\(L[i][i]=a[i]\)
  • 变化方法:从左侧拿走一些石子或者从右侧拿走一些石子
  • 让我们使用\(L[i][j-1]\)\(R[i][j-1]\)来表示\(L[i][j]\)\(R[i][j]\),形成\(DP\)递推关系。

想像一个通用场景:假设现在前面动作都按要求整完了,问我们:本步骤,我们有哪些变化,根据这些变化,怎么样用前面动作积累下来的数据来完成本步骤数据变化的填充。这不就是动态规划吗?

3、推论1

有了上面谁的\(L[i][j]\)唯一性,得出一个有用的推论:
对于任意非负整数 \(x \neq L(i,j)\)\(\large (x,a_i,a_{i+1},\cdots,a_j)\)为必胜局面。

4、推论2

为方便叙述,下文记 \(L[i][j-1]\)\(L\),记 \(R[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\)


4、分类讨论

  • \(x=R\)\(Case\) \(1\)
    最简单的情况——根据 \(R[i][j-1]\) 的定义,区间 \([i,j]\) 本来就是必败局面,故

    \[\LARGE L[i][j]=0 \]

  • \(x<R\)

    • \(x<L\),即 \(x< \min\{L,R\}\)\(Case\) \(2\)
      • 结论:

      \[\LARGE L[i][j]=x \]

      • 证明
        即证 \(\large (x,a_i,a_{i+1},\cdots,a_{j-1},x)\)为必败局面。
        由于最左边和最右边的两堆石子数量相同,后手可进行和先手对称的操作,后手必将获得一个形如\((y,a_i,a_{i+1},⋯,a_{j−1})\)\((a_i,a_{i+1},\cdots,a_{j-1},y)\) 的局面,其中: \(0<y \le x<\min\{L,R\}\)
        结合 \(L(i,j-1)\)\(R(i,j-1)\) 的定义知这个局面必胜,即后手必胜,先手必败,证毕。
        只有左侧为\(L=L(i,j-1)\)这个唯一值时,才是必败态,现在不是\(L=L(i,j-1)\),而是\(y<min(L,R)\),所以后手必胜,即先手必败。

      • 注意上述证明的前提是 \(x \neq 0\),因此后续证明若使用 \(Case\) \(2\),必须满足 \(x \neq 0\)(具体见后文)。


    • \(x \geq L\),即 \(L \leq x < R\)\(Case\) \(3\)
      • 结论:$$\LARGE L[i][j]=x+1$$

      • 证明
        即证 \((x+1,a_i,a_{i+1},\cdots,a_{j-1},x)\)必败局面

        • 若先手拿最左边一堆,设拿了以后还剩 \(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\),此时最右堆石子数 \(k\) 满足 \(L \le k<R\),结合 \(R[i][j-1]\) 定义知局面必胜。

        • 若先手拿最右边一堆,设拿了以后还剩 \(z\) 个石子。
          \(z \ge L\),则后手将最左堆拿成 \(z+1\)个石子,递归证明即可。
          \(0<z<L\),则后手将最左堆拿成 \(z\) 个石子,由 \(Case\) \(2\) 知此时是必败局面。
          \(z=0\),则后手将最左堆拿成 \(L\) 个石子,由 \(L[i][j-1]\)定义知此时局面必败。

  • \(x>R\)
    • \(x≤L\),即 \(R < x \leq L\)\(Case\) \(4\)

      • 结论:$$\LARGE L[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\)),由 \(R[i][j-1])\) 的定义知此时是必败局面。

        • 若先手拿最右边一堆,设拿了以后还剩 \(z\) 个石子。
          • \(z>R\),则后手将最左边一堆拿成 \(z-1\) 个石子(注意 \(z-1 \ge R >0\)),递归证明即可。保证右侧比左侧多\(1\)个石子。
          • \(z=R\),则后手把最左堆拿完,根据 \(R[i][j-1]\)的定义可知得到了必败局面。
          • \(0<z<R\),则后手将最左堆拿成 \(z\) 个石子,由 \(Case\) \(2\) 知此时是必败局面。
          • \(z=0\),此时最左堆石子数量 \(k\) 满足 \(0<k<L\),结合 \(L[i][j-1]\) 定义知局面必胜。

    • \(x>L\),即 \(x>\max\{L,R\}\)\(Case\) \(5\)

      • 结论:$$\LARGE L[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;
}
posted @ 2022-07-05 15:25  糖豆爸爸  阅读(70)  评论(0编辑  收藏  举报
Live2D