1 条题解
-
0
思路与算法
令 表示剩余 颗时轮到操作的人能否在双方最优下获胜。空局面没有合法移动,。从 可以到达所有 ,所以当且仅当这些后继中至少有一个必败态, 才必胜。
不逐一枚举最多K个移动,而维护上述窗口内必败态的个数 。初始窗口只有状态0,计数为1。每次先据计数计算 ,再把状态 加入窗口;如果 ,将最旧状态 移出,为下一轮准备。最后必胜输出1,必败输出2。
正确性说明
游戏每次至少取1颗,总数严格减少,必然终止。终态0为败;非终态若能走到败态,当前玩家可选择该步而必胜,若全部后继都胜,对手总有必胜策略,当前玩家必败。因此胜负递推成立。
计算 前,维护窗口恰为所有合法移动得到的后继状态,计数精确等于其中必败态数量。查询后加入 并在需要时删去 ,窗口变成下一状态的后继范围,保持不变式。由初始状态归纳,所有 都正确,最后输出符合协议。
也可从递推归纳出 的倍数为败态:它的所有合法后继都不是倍数;其余状态能取去余数到达倍数。此结论作为独立答案核验,实际参考程序仍使用线性窗口DP。
复杂度
初始化胜负数组 。每个状态只做常数次窗口加入、删除和判断,总时间 ,额外空间 。 可与 同阶,不能用 的逐移动枚举处理最大数据。
易错点
- 正常玩法,最后取光者胜,故没有石子的轮次属于败态。
- 必胜条件是存在一个败后继,不是所有后继均败。
- 当前状态要先判定再加入窗口,旧状态在判完后才移出,避免差一。
- 输出是整数1/2,不是多堆题的win/lose。
- 覆盖 、、、 及最大 。原公开候选中超过当前 的整点拒用;独立模类结论与一万次直接合法移动递推交叉核对。
参考代码(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