1 条题解

  • 0
    @ 2026-10-26 6:11:32

    思路与算法

    单堆大小为x的SG值记为g[x],g[0]=0。合法取数来自给定递增集合B,因此g[x]=mex({g[x-b] | b在B中且b<=x})。按x从小到大计算到最大初始堆大小A。后继数至多M,SG值至多M,用M+1大小的时间戳数组记录后继值。

    设所有堆SG异或为z。若z=0,输出NO。否则输出YES,按堆编号从小到大、允许取数从小到大枚举第一步。选取第一个满足z xor g[A_i] xor g[A_i-b]=0的方案,输出堆编号与b,立即结束。

    正确性说明

    单堆每步至少减少一颗石子,递推图无环。按石子数归纳,较小后继的SG均已正确,取mex便得到当前SG。不同堆互不影响,正常玩法下整体SG等于各堆SG异或;为0时所有合法后继非零,为非零时存在合法后继归零。故NO与YES判断正确。

    从第i堆取b时,其他堆不变,新的异或恰为z xor g[A_i] xor g[A_i-b]。枚举条件因此准确筛出所有能够转移到必败态的第一步。非零局面必存在此类方案。外层编号递增,内层取数递增,第一个符合条件的方案先最小化堆编号,再最小化取数,满足输出要求。

    复杂度

    令A为最大初始堆大小。分配和初始化SG数组O(A),标记数组O(M),读取堆与取数O(N+M)。每个x标记至多M个后继,mex至多M,共O(AM)。异或O(N),策略搜索最坏O(NM)。总时间O(A+M+AM+NM+N),额外空间O(A+M+N)。

    易错点

    1. 只允许从给定集合中取数,不能套普通Nim的直接石子数异或。
    2. 只枚举b<=当前堆大小的合法操作;若所有b都更大,SG为0。
    3. YES之后必须输出获胜策略,NO后不输出第二行。
    4. 编号从1开始,先比较编号再比较取数。输入已经递增,找到第一步立即返回。
    5. 标记值使用当前x作为时间戳,避免反复清空数组;mex最大为M,数组需要M+1格。

    参考代码(C++11)

    #include <algorithm>
    #include <iostream>
    #include <vector>
    using namespace std;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n;
        if (!(cin >> n))
            return 0;
        vector<int> a(n);
        int limit = 0;
        for (int &x : a) {
            cin >> x;
            limit = max(limit, x);
        }
        int m;
        cin >> m;
        vector<int> b(m);
        for (int &x : b)
            cin >> x;
        vector<int> sg(limit + 1), seen(m + 1, -1);
        for (int x = 1; x <= limit; ++x) {
            for (int t : b) {
                if (t > x)
                    break;
                seen[sg[x - t]] = x;
            }
            while (seen[sg[x]] == x)
                ++sg[x];
        }
        int sum = 0;
        for (int x : a)
            sum ^= sg[x];
        if (!sum) {
            cout << "NO\n";
            return 0;
        }
        cout << "YES\n";
        for (int i = 0; i < n; ++i)
            for (int t : b) {
                if (t > a[i])
                    break;
                if ((sum ^ sg[a[i]] ^ sg[a[i] - t]) == 0) {
                    cout << i + 1 << ' ' << t << '\n';
                    return 0;
                }
            }
    }
    
    • 1

    信息

    ID
    1676
    时间
    1000ms
    内存
    512MiB
    难度
    普及+
    标签
    递交数
    1
    已通过
    1
    上传者