数学-博弈论. 拆分-Nim游戏

c++

AcWing 894. 拆分-Nim游戏

/*
 *  题目描述:
 *      AcWing 894. 拆分-Nim游戏:
 *      给定 n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,
 *      然后放入两堆规模更小的石子(**新堆规模可以为 0,且两个新堆的石子总数可以大于取走的那堆石子数**),
 *      最后无法进行操作的人视为失败。
 *      问如果两人都采用最优策略,先手是否必胜。
 *
 *      输入格式:
 *      第一行包含整数 n。
 *      第二行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 ai。
 *
 *      输出格式:
 *      如果先手方必胜,则输出 Yes。
 *      否则,输出 No。
 *
 *      数据范围
 *      1 ≤ n, ai ≤ 100
 *  解题思路:
 *      在 AcWing 893. 集合-Nim游戏 中,我们学习了 mex 运算 和 SG 函数,
 *      并初步了解 SG 函数在判断必胜态和必负态的的作用我们还细究了 SG 函数为什了要求 mex 运算,这和普通 Nim 函数的关系;
 *      那么在本题中,我们能否使用 SG 函数呢?
 *
 *      首先给定的了 n 堆石子 a1, a2, ..., an,只要我们求得 SG(a1), ..., SG(an) 通过与运算就可以得知最后的必胜态和必负态。
 *      那么 SG(ai) 如何计算,ai 石子可以分为若干个状态,我们求解每个局面的 SG 函数,然后 mex 操作即可以求出当前局面的 SG 值。
 *      但是,ai 分为的若干的局面是两堆石子,两堆石子如何计算 SG 函数?
 *      
 *      **SG函数理论**,多个独立局面的 SG 值,等于这些局面 SG 值的异或和。
 *      
 *      由该理论可知,我哦们分别计算每堆石子的 SG 数值,然后两堆石子异或即可以求出两堆石子作为局面的 SG 数值。
 *      
 *      SG 函数理论我们不进行证明(因为我没看)
 *      
 *      但是,如果细致的来看 普通 Nim 游戏的话,是可以看到 SG 函数和 Mex 操作的影子的。
 *          首先,第 i 堆石子 ai 的 SG 函数 SG(ai) = ai。这是因为,他可以到达 ai - 1, ai - 2, ..., 0 多个局面
 *          SG(0) = 0, SG(1) = 1, ..., SG(x) = x
 *          其次,如果将 a1 看成一个局面, a2 看成一个局面, ..., an 看成一个局面,这些独立局面的合并
 *          即为多个局面的异或 a1 ^ a2 ... ^ an 就是我们最终的结果。
 *      
 *      同理在集合 Nim 游戏中,最后 SG(a1) ^ SG(a2) ^ ... ^ SG(an) 也是多个独立局面 SG 数值的求解
 *
 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;

const int N = 110, M = 110;
int a[N], n;
int sg[M];

int get_sg(int x) {
    if (sg[x] != -1) {
        return sg[x];
    }

    set<int> s;
    for (int i = 0; i < x; i ++ ) {
        for (int j = 0; j <= i; j ++ ) {
            s.insert(get_sg(i) ^ get_sg(j));
        }
    }

    for (int i = 0; ; i ++ ) {
        if (s.count(i) == 0) {
            sg[x] = i;
            break;
        }
    }

    return sg[x];
}


bool solution() {
    // initialize
    int res = 0;
    memset(sg, -1, sizeof sg);

    // calculate
    for (int i = 1; i <= n; i ++ ) {
        res = (res ^ get_sg(a[i]));
    }

    return res != 0;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) {
        scanf("%d", &a[i]);
    }

    if (solution()) {
        puts("Yes");
    } else {
        puts("No");
    }

    return 0;
}


posted @ 2022-07-25 21:41  lucky_light  阅读(132)  评论(0编辑  收藏  举报