1 条题解

  • 0
    @ 2026-10-26 5:35:26

    思路与算法

    令 win[i]win[i] 表示剩余 ii 颗时轮到操作的人能否在双方最优下获胜。空局面没有合法移动,win[0]=falsewin[0]=false。从 ii 可以到达所有 j∈[max⁡(0,i−K),i−1]j\in[\max(0,i-K),i-1],所以当且仅当这些后继中至少有一个必败态,ii 才必胜。

    不逐一枚举最多K个移动,而维护上述窗口内必败态的个数 losinglosing。初始窗口只有状态0,计数为1。每次先据计数计算 win[i]win[i],再把状态 ii 加入窗口;如果 i≥Ki\ge K,将最旧状态 i−Ki-K 移出,为下一轮准备。最后必胜输出1,必败输出2。

    正确性说明

    游戏每次至少取1颗,总数严格减少,必然终止。终态0为败;非终态若能走到败态,当前玩家可选择该步而必胜,若全部后继都胜,对手总有必胜策略,当前玩家必败。因此胜负递推成立。

    计算 ii 前,维护窗口恰为所有合法移动得到的后继状态,计数精确等于其中必败态数量。查询后加入 ii 并在需要时删去 i−Ki-K,窗口变成下一状态的后继范围,保持不变式。由初始状态归纳,所有 win[i]win[i] 都正确,最后输出符合协议。

    也可从递推归纳出 K+1K+1 的倍数为败态:它的所有合法后继都不是倍数;其余状态能取去余数到达倍数。此结论作为独立答案核验,实际参考程序仍使用线性窗口DP。

    复杂度

    初始化胜负数组 O(N)O(N)。每个状态只做常数次窗口加入、删除和判断,总时间 O(N)O(N),额外空间 O(N)O(N)。KK 可与 NN 同阶,不能用 O(NK)O(NK) 的逐移动枚举处理最大数据。

    易错点

    1. 正常玩法,最后取光者胜,故没有石子的轮次属于败态。
    2. 必胜条件是存在一个败后继,不是所有后继均败。
    3. 当前状态要先判定再加入窗口,旧状态在判完后才移出,避免差一。
    4. 输出是整数1/2,不是多堆题的win/lose。
    5. 覆盖 N=1,K=1N=1,K=1、K=NK=N、K=N−1K=N-1、K=1K=1 及最大 NN。原公开候选中超过当前 N≤105N\le10^5 的整点拒用;独立模类结论与一万次直接合法移动递推交叉核对。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n, k;
        cin >> n >> k;
        vector<char> win(n + 1);
        int losing = 1;
        for (int i = 1; i <= n; i++) {
            win[i] = losing > 0;
            if (!win[i])
                losing++;
            if (i >= k && !win[i - k])
                losing--;
        }
        cout << (win[n] ? 1 : 2) << '\n';
    }
    
    • 1

    信息

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