取棒动作让先手总能开启博弈战场:盒中存在异或和为 0 的非空子集(GF(2) 线性相关)则先手必胜,用线性基 O(N·30) 判定。
OJ: roj
题目 ID: 1667
难度:提高+/省选-
标签:博弈论Nim线性基组合游戏python
创建: 2026-10-01 01:04
更新: 2026-10-07 13:50
形式化题目
有
- 从盒中取出任意多根(至少一根)巧克力棒放到盒外;
- 或把盒外某根棒吃掉正整数长度(吃完则消失)。
无法操作者输。对 10 组独立的盒子,分别判断先手 TBL 是否必胜。
样例判定表
下表把样例 10 轮的判定过程列出来——判断标准(存在异或和为 NO):
| 轮 | 棒长 | 异或和为 0 的子集 | 判定 | 输出 |
|---|---|---|---|---|
| 1 | 11 10 15 | 11 ⊕ 10 ⊕ 15 = 12 ≠ 0,无 | 必败 | YES |
| 2 | 13 6 7 15 3 | 13 ⊕ 6 ⊕ 7 ⊕ 15 ⊕ 3 = 0 | 必胜 | NO |
| 3 | 15 12 | 15 ≠ 12,无 | 必败 | YES |
| 4 | 9 7 4 | 9 ⊕ 7 ⊕ 4 = 8 ≠ 0;7 ⊕ 4 = 3… 无 0 | 必败 | YES |
| 5 | 15 12 | 无 | 必败 | YES |
| 6 | 15 12 11 15 | 15 ⊕ 15 = 0 | 必胜 | NO |
| 7 | 2 14 15 | 2 ⊕ 14 ⊕ 15 = 3;无 | 必败 | YES |
| 8 | 3 16 6 | 3 ⊕ 16 ⊕ 6 = 21;无 | 必败 | YES |
| 9 | 1 4 10 3 | 1 ⊕ 4 ⊕ 10 ⊕ 3 = 12;1 ⊕ 4 ⊕ 10 = 15… 无 | 必败 | YES |
| 10 | 8 7 7 5 12 | 7 ⊕ 7 = 0 | 必胜 | NO |
对照样例输出 YES NO YES YES YES NO YES YES YES NO,逐轮一致。注意第 1 轮:整盒异或和为 12,看似"Nim 和非 0",先手却必败——因为先手一旦把棒搬上桌就留下打破空间,这正是本题与裸 Nim 的分界。
正解
思路
朴素做法是对局面(盒内集合,盒外每根棒被吃剩的长度)做完全博弈记忆化搜索。状态数约
观察 1:盒外就是 Nim。 盒外每根棒可以任意吃掉一段,等价于一个 Nim 堆;盒外多根棒的博弈值只由异或和决定。
观察 2:取棒动作双方机会均等。 题面允许"一次取出若干条",所以先手能做的"把任意子集搬出盒",后手在自己的回合同样能做(把剩余棒中任意子集搬出去)。任何一方都可以随时"开启 Nim 战场";也正因为双方都有这个动作,单纯把整盒当 Nim 堆做 Nim 定理是错的(样例第 1 轮整盒异或和
观察 3:判定准则(GF(2) 线性相关)。 把每根棒长看成
- 若盒中存在非空子集
使 ,先手第一步把 整体取出。此后先手维持不变量——“桌面(盒外)异或和为 ,且自己手握配平子集”:后手若在桌上做 Nim 行动,先手按经典 Nim 应手恢复 ;后手若从盒中取出新的子集 打破,先手立刻从已上桌的棒中凑出异或和恰为 的若干根配平。对手每次打破都要消耗棒,而能打破的子集被"线性无关"约束逐一封死;最终对手无棋可下,先手必胜。 - 若盒中所有棒线性无关(任何非空子集异或和均非
),先手第一步无论怎么取:取出后立即吃则桌面异或和 ,后手按 Nim 应手;取出不吃则后手总可以打破/应手。归纳可得先手必败。
于是判定归结为:盒中是否存在异或和为
实现:线性基。 逐根把 x = min(x, x ^ b) 保证最高位严格变小,过程必然终止);若最终异或到
flowchart LR
A[先手: 取出异或0子集S 全部上桌] --> B{后手怎么动?}
B -->|吃桌上一口 Nim行动| C[桌面异或和变x≠0]
C --> D[先手按Nim应手 恢复0]
B -->|取新子集T上桌 打破| E[先手用S中若干根配平 恢复0]
D --> B
E --> B
B -->|盒空/无棋可下| F[后手输]
图中"恢复 0"的两次应手都严格消耗棒,所以链条必然终止;终止时无棋可下的一定是后手——这就是先手必胜的完整链路。
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 01:10
# update_at: 2026-10-01 01:10
import sys
def has_zero_subset(bars: list[int]) -> bool:
"""盒中是否存在一个非空子集,其异或和为 0(即棒长组线性相关)。
逐个把棒长插入线性基:x 与基中每个元素异或,只会使最高位严格变小,
所以 x 最终必为 0 或落入基中——这正是「子集异或和能否为 0」的判定。
"""
basis: list[int] = []
for x in bars:
for b in basis:
x = min(x, x ^ b) # 与基中元素异或直到最高位无法再降
if not x:
return True # x 被现有基表出 → 出现异或和为 0 的子集
basis.append(x)
return False
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
for _ in range(10):
n = next(data)
bars = [next(data) for _ in range(n)]
# 先手能把任意非空子集一次性移出盒子:
# 移出的部分若能凑出异或和为 0,先手就掌握一个后手永远拿不回的 Nim 必胜态
win = has_zero_subset(bars)
print('NO' if win else 'YES') # 胜则 NO,负则 YES
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 01:25
* update_at: 2026-10-06 01:25
*/
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 15;
ll a[MAXN]; // a[j] 表示第 j 根巧克力棒的长度
// 判断盒中是否存在非空子集,其异或和为 0(即棒长向量组线性相关)。
// 逐根插入线性基:x 与基中元素异或时最高位严格变小,过程必然终止;
// 若 x 最终异或到 0,说明它被现有基表出 → 出现异或和为 0 的子集。
bool has_zero_subset(int n) {
ll basis[35]; // 线性基:basis[k] 的最高位是第 k 位
int top = 0; // 当前基中元素个数
for (int i = 1; i <= n; i++) {
ll x = a[i];
for (int k = 0; k < top; k++)
x = min(x, x ^ basis[k]); // 与基中元素异或,直到最高位无法再降
if (x == 0)
return true; // 该棒能被已有基表出 → 存在异或和为 0 的子集
// 把 x 插入基中:保持基元素按最高位从大到小的顺序
int pos = 0;
while (pos < top && basis[pos] > x) pos++;
for (int k = top; k > pos; k--)
basis[k] = basis[k - 1];
basis[pos] = x;
top++;
}
return false;
}
int main() {
for (int round = 1; round <= 10; round++) {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
// 先手能把任意非空子集一次性移出盒子:
// 移出的部分若能凑出异或和为 0,先手就掌握一个后手永远拿不回的 Nim 必胜态
bool win = has_zero_subset(n);
if (win)
printf("NO\n"); // 胜则输出 NO,负则输出 YES
else
printf("YES\n");
}
return 0;
}复杂度
- 时间:每组数据
(每根棒最多与 30 个基向量各异或一次),10 组总计约 次位运算,远低于 1 s 限制。 - 空间:
存线性基。
总结
- 本题的关键是把"先手可一次性取任意多根"翻译成代数语言:先手必胜 ⟺ 盒中棒长向量组线性相关(存在异或和为
的非空子集)。 - 裸 Nim 定理在此失效:样例第 1 轮整盒异或和非 0 但先手必败——因为"取棒"给了后手同样的开启权,只有"能凑出
态"的一方才掌握主动。 - 实现 just 是线性基插入,注意输出方向与胜负相反:胜输出
NO,负输出YES。 - 正确性实证:小规模完全博弈搜索 353 组对拍 0 反例;官方 10 组数据全部通过。