• 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;
    }
    
  • 相关阅读:
    Spring配置文件的命名空间URI
    Hibernate @Embeddable注释
    HIbernate实体类注解配置
    Hibernate关系映射之many-to-many
    Hibernate中cascade属性的区别
    Hibernate注解配置与XML配置区别
    JPA关系映射之one-to-one
    Mysql修改id自增值
    JPA关系映射之one-to-many和many-to-one
    swift
  • 原文地址:https://www.cnblogs.com/littlehb/p/16446629.html
Copyright © 2020-2023  润新知