题解:AcWing 893 集合-Nim游戏

【题目来源】

AcWing:893. 集合-Nim游戏 - AcWing题库

【题目描述】

给定 \(n\) 堆石子以及一个由 \(k\) 个不同正整数构成的数字集合 \(S\)

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 \(S\),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

【输入】

第一行包含整数 \(k\),表示数字集合 \(S\) 中数字的个数。

第二行包含 \(k\) 个整数,其中第 \(i\) 个整数表示数字集合 \(S\) 中的第 \(i\) 个数 \(s_i\)

第三行包含整数 \(n\)

第四行包含 \(n\) 个整数,其中第 \(i\) 个整数表示第 \(i\) 堆石子的数量 \(h_i\)

【输出】

如果先手方必胜,则输出 Yes

否则,输出 No

【输入样例】

2
2 5
3
2 4 7

【输出样例】

Yes

【算法标签】

《AcWing 893 集合-Nim游戏》 #数学知识# #博弈论# #SG函数#

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 110, M = 10005; // 定义常量 N 和 M
int n, k; // n 表示石子的堆数,k 表示每次可以取的石子数的种类数
int s[N]; // s 数组存储每次可以取的石子数
int f[M]; // f 数组用于记忆化搜索,存储每个状态的 SG 值

// 计算 SG 值
int sg(int x)
{
    if (f[x] != -1) return f[x]; // 如果已经计算过,直接返回
    set<int> S; // S 用于存储所有可能的 SG 值
    for (int i = 0; i < k; i++) { // 遍历所有可能的取石子数
        if (x >= s[i]) S.insert(sg(x - s[i])); // 递归计算 SG 值并插入 S
    }
    for (int i = 0; i <= x; i++) { // 找到最小的未出现在 S 中的非负整数
        if (S.count(i) == 0) return f[x] = i; // 返回该整数作为 SG 值
    }
}

int main()
{
    cin >> k; // 输入每次可以取的石子数的种类数 k
    for (int i = 0; i < k; i++) cin >> s[i]; // 输入每次可以取的石子数
    cin >> n; // 输入石子的堆数 n
    memset(f, -1, sizeof(f)); // 初始化 f 数组为 -1(表示未计算)

    int ans = 0; // 初始化异或结果为 0
    for (int i = 0; i < n; i++) { // 遍历每堆石子
        int x; // 定义变量 x,表示当前堆的石子数
        cin >> x; // 输入当前堆的石子数
        ans ^= sg(x); // 计算当前堆的 SG 值并与异或结果进行异或
    }

    if (ans) puts("Yes"); // 如果异或结果不为 0,输出 Yes
    else puts("No"); // 否则输出 No

    return 0; // 程序结束
}

【运行结果】

2
2 5
3
2 4 7
Yes
posted @ 2026-02-25 07:50  团爸讲算法  阅读(0)  评论(0)    收藏  举报