AcWing 1322. 取石子游戏

题目传送门

参考题解

一、状态定义

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

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

L[i][j] ai ai+1 ... aj1 aj R[i][j]

即:(L[i][j],ai,ai+1,,aja[i]a[j]),(ai,ai+1,,aja[i]a[j],R[i][j])先手必败局面。


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

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

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

x,ai,ai+1,,ajA(x)

都为必胜局面,记为 A(x) 局面。

由于 A(x) 为必胜局面,故从 A(x) 局面 必然存在M种一步可达必败局面

若从最左边一堆中拿,因为假设原因,不可能变成必败局面,因为这样得到的局面仍形如 A(x)

注意包括此行在内的接下来几行默认 x0

左边拿没用,只能考虑从右边拿:
于是设 A(x) 一步可达的(某个)必败局面(x,ai,ai+1,,aj1,y),显然有 0y<aj

由于 x 有无限个,但 y 只有 aj种——根据抽屉原理,必存在 x1,x2(x1x2),y 满足 (x1,ai,ai+1,,aj1,y)(x2,ai,ai+1,,aj1,y) 都是必败局面。但这两个必败局面之间实际一步可达,故矛盾,进而原命题成立。

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

反证法:
假设 L(i,j) 不唯一,则存在非负整数 x1,x2(x1x2),使得(x1,ai,ai+1,,aj1,aj)(x2,ai,ai+1,,aj1,aj) 均为必败局面。而这两个必败局面之间实际一步可达,故矛盾,进而原命题成立。


四、状态转移

1、边界情况

L[i][i]=ai

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

2、场景分析

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

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

3、推论1

有了上面谁的L[i][j]唯一性,得出一个有用的推论:
对于任意非负整数 xL(i,j)(x,ai,ai+1,,aj)为必胜局面。

4、推论2

为方便叙述,下文记 L[i][j1]L,记 R[i][j1]R,并令 x=aj(x>0)

R=0L=R=0,此时 x>max{L,R},也就是说 L=0R=0 都属于 Case 5,故其它 Case 满足 L,R>0

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


4、分类讨论

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

    L[i][j]=0

  • x<R

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

      L[i][j]=x

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

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


    • xL,即 Lx<RCase 3
      • 结论:L[i][j]=x+1

      • 证明
        即证 (x+1,ai,ai+1,,aj1,x)必败局面

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

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

  • x>R
    • xL,即 R<xLCase 4

      • 结论:L[i][j]=x1

      • 证明

        • 若先手拿最左边一堆,设拿了以后还剩 z 个石子。
          • zR,则后手将最右堆拿成 z+1 个石子,保证左侧比右侧多1个石子,就能回到 Case 4 本身,递归证明即可。
          • 0<z<R,则后手将最右堆拿成 z 个石子,由 Case 2 知此时是必败局面。
          • z=0,则后手将最右堆拿成 R 个石子(注意 Case 4 保证了此时最右堆石子个数 >R),由 R[i][j1]) 的定义知此时是必败局面。

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

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

      • 结论L[i][j]=x

      • 证明
        设先手将其中一堆拿成了 z 个石子。

        • z>max{L,R},后手将另一堆也拿成z个,回到 Case 5,递归证明。

        • 0<z<min{L,R},后手把另一堆也拿成 z 个石子即可转 Case 2

        • z=0,将另一堆拿成 LR 个石子即可得到必败局面。

        • 剩余的情况是 LzRRzL
          Case 3 可以解决最左堆 L+1zR,最右堆 LzR1 的情况
          Case 4 可以解决最左堆 RzL1,最右堆 R+1zL的情
          况。

        ​所以只需解决最左堆 z=L 和最右堆 z=R 的情况。而这两种情况直接把另一堆拿完就可以得到必败局面。


综上所述:

L[i][j]={0x=Rx+1Lx<Rx1R<xLxotherwise

温馨提示:请看清楚 L 取不取等,乱取等是错的!

同理可求 R(i,j)

回到原题,先手必败当且仅当 L[2][n]=a1 ,于是我们就做完啦!

时间复杂度 O(n2)


五、实现代码

#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 @   糖豆爸爸  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-07-05 开发一个代码的自动生成器,使用Jfinal4.3+Swagger+Sql
2019-07-05 定时任务不执行处理一例
2019-07-05 yum 异常解决一例
2019-07-05 数据池项目演示地址及帐号
2017-07-05 判断浏览器
2015-07-05 厦门海沧区磁盘只有1TB的解决方案
Live2D
点击右上角即可分享
微信分享提示