zoukankan      html  css  js  c++  java
  • 【博弈论】K倍动态减法游戏

    HDU 2486 A simple stone game

    有n个石子,两个游戏者轮流操作,第一个操作的人最多能拿走n-1个石子,以后,每个游戏者最多能拿走前一个游戏者拿走数目的k倍,如果先手必败输出lose,否则输出必胜的情况下第一步拿走的石子数。

    这就是K倍动态减法游戏,可以参考《曹钦翔从“k倍动态减法游戏”出发探究一类组合游戏问题》的论文。

    首先k=1的时候,必败态是2 ^ i, 因为我们把数按二进制分解后,拿掉二进制的最后一个1,那么对方必然不能拿走倒数第二位的1,因为他不能拿的比你多。你只要按照这个策略对方一直都不可能拿完,而且一定会生成新的低位的1。所以你就会赢。而当分解的二进制中只有一个1时,因为第一次先手不能全部取完,所以后手一定有办法取到最后一个1,所以必败!

    k=2的时候,即为斐波那契博弈,必败态是斐波那契数列,这里用到一个斐波那契数列的性质,即任何数都可以表示成若干个“互不相邻的”斐波那契数的和,而不相邻的斐波那契数所差的倍数都是大于2的,那么我们就可以类比K=1的情况,把N按这种“斐波那契数列”的数制分解,每次仍然是取走最低位的1,由于后手无法取走高两位之上的1而前边的不相邻有保证了不会有连续的1出现,所以接下来就和K=1的时候一样了,每次取走最低位的1直到结束。

    k>2的时候,犹如Fibonacci博弈,我们首先要求一个数列,将n分解成数列中一些项的和,然后就可以按Fibonacci博弈的解决方法来完成,也可以按二进制的方法来理解,每次取掉最后一个1 还是符合上面的条件。

    我们用a数组表示要被求的数列,b数组中的b[i]保存 a[0...i] 组合能够构造的最大数字。这儿有点难理解,所谓构造就是指n分解为Fib数相加的逆过程。举例说明,当k = 2 时,a[N]={1, 2, 3, 5, 8, 13, 21, 33....} (Fibonacci数组);那么b[3] 即 1、2、 3 能够构造的最大数字,答案是4,有点匪夷所思?或许你会问为什么不是5、6或者其它的什么,其实是这样的 ,4 能分解成 1+3 是没有争议的,但5能分解成2+3吗? 不能,因为5本身也是Fibonacci数;6虽然能分解,但不是分解成1+2+3,而是分解成1+5。

    经过上述,我们知道b[i] 是 a[0...i] 能够构造出的最大数字,那么a[i +1] = b[i]+1;因为a数组(Fib数组)所存的数字都是不可构造的(取到它本身就是必败态),显然a[0...i]构造的最大数字 + 1 即为下一个不可构造的数字了(a[i + 1])。

    然后关于b[i]的计算,既然是a[0...i]构造最大数字,那么 a[i]是一定要选用的(这儿需要一定的推理,a[i]构造数字时,相邻的j个是不能同时用的,就像上述的2、3不能构造出5一样,推理请自己完成),那么要选用的下一项只能递减寻找,直到找到 a[t] 满足 a[t] * K < a[i] ,而b[t]就是a[0...t]所能构造的最大数字,再加上a[i], 即为a[0...i]能构造的最大数字,于是b[i] = b[t] + a[i]。

    求得数列后,之后的工作就简单了,跟Fibonacci博弈一样一样的,如果n=数列中的数,则必败,否则必胜;必胜时还要求输出第一步取法,按照上文的理解,将n分解之后,选择最小的一个a[i]即可(类似选择二进制的最小的1)。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    typedef long long LL;
    int k;
    int flag = 0;
    LL a[N], b[N];
    int main() {
        int T;
        scanf("%d", &T);
        int cases = 0;
        while (T--) {
            cases++;
            LL m, n;
            cin >> n >> m;
            a[0] = b[0] = 1;
            int i = 0, j = 0;
            while (n > a[i]) {
                i++;
                a[i] = b[i - 1] + 1;  //首先求出当前的a数组
                while (a[j + 1] * m < a[i]) j++;
                if (a[j] * m < a[i])
                    b[i] = b[j] + a[i];  //然后根据a数组求b数组
                else
                    b[i] = a[i];
            }
            if (n == a[i])
                printf("Case %d: lose
    ", cases);
            else {
                LL res = a[i];
                while (n) {
                    if (n >= a[i]) n -= a[i];
                    res = a[i];
                    i--;
                }
                printf("Case %d: %lld
    ", cases,res);
            }
        }
        return 0;
    }
    
  • 相关阅读:
    YOLO V5 is Here! Custom Object Detection Tutorial with YOLO V5
    pjsip application notes
    Tilt Angle Visualization With Edison, Accelerometer and Python
    WebRTC 镜像源
    Get Started with WebRTC
    sqlserver关于发布订阅replication_subscription的总结
    在Flask中,g是什么?它的生命周期是?能做什么?
    46.全排列问题
    【LeetCode】代码模板,刷题必会
    QEMU+KVM学习笔记
  • 原文地址:https://www.cnblogs.com/dyhaohaoxuexi/p/14424050.html
Copyright © 2011-2022 走看看