1 条题解
-
0
E. Not a Nim Problem 完整题解(基于标程)
一、题目重述
有 堆石子,第 堆初始有 个石子。
两人轮流操作,Alice 先手。
每回合,玩家选择一堆石子,设当前该堆有 个石子,可以取走 个石子(),但要求 。
无法操作的玩家输。问:双方都采取最优策略时,谁获胜?
数据范围: 总和 ,。
二、转化为 impartial 博弈
这是一个 impartial 组合游戏(双方操作相同),可以用 Sprague–Grundy 定理求解:
- 整个游戏的 SG 值为各堆 SG 值的异或和:$$\text{SG}_{\text{total}} = \bigoplus_{i=1}^n g(a_i) $$
- 若 ,则先手(Alice)获胜;否则 Bob 获胜。
因此,问题转化为:计算每个正整数 的 Grundy 数 。
三、单堆游戏的 Grundy 数定义
对于一堆 个石子,定义 为它的 Grundy 数:
$$g(x) = \text{mex} \{\, g(x - y) \mid 1 \le y \le x,\ \gcd(x, y) = 1 \,\} $$其中 表示最小的非负整数不在集合中。
边界条件:(没有石子,无法操作)。
四、标程的核心结论
标程中
grundy(x)函数的计算逻辑是:int grundy(int x) { if (x == 1) return 1; int cnt = 0; int last = 0; while (x > 1) { int p = spf[x]; if (p != last) { cnt++; last = p; } x /= p; } return cnt % 2; }这个函数计算的是: 的不同质因数个数()的奇偶性,并对 特殊处理为 。
即:
$$g(x) = \begin{cases} 1, & \text{if } x = 1 \\ \omega(x) \bmod 2, & \text{if } x > 1 \end{cases} $$其中 表示 的不同质因数的个数。
五、验证小数据
我们手工计算 并对比之前的小范围 Grundy 值,验证这个结论是否正确。
标程 之前手工计算 1 0 1(特判) 1 2 1 0 ❌ 发现矛盾: 时,手工计算 ,但标程返回 。
难道是标程错了?不,标程能通过本题,说明本题的 Grundy 数定义可能与我们手工计算的不同?或者手工计算有误?重新手工计算 :
- ,可取的 :必须满足 ,所以 只能是奇数且 ,即 。
- 到达状态 , 是多少?需要先确定 。
- ,可取 (),到达 ,,所以 。
- 因此 。
手工计算没错。那么标程为何返回 ?唯一解释:本题的博弈规则中,从 时是否允许取 ?
条件: 才不允许,即 允许。
,所以不允许。所以手工计算正确。但标程能 AC,说明实际通过的代码中,对于偶数的处理可能不同。再检查标程:它用的是
cnt % 2,对于 ,不同质因数个数 ,,但实际答案是 。
这只能说明:标程中的grundy并非直接返回 ,而是有其他含义。仔细看标程:它统计的是不同质因数的个数
cnt,然后返回cnt % 2。对于 ,cnt=1,返回 。
但根据题目样例,第一个样例3 2 9输出Bob,说明异或和为 。
计算: 按此公式:,返回 ;;:,不同质因数 ,cnt=1,返回 。异或和 ,会输出Alice,但样例输出是Bob,矛盾。这说明:标程可能不是用这个公式?或者我误解了样例?
再检查样例1:3 2 9输出Bob。若 (手工算得),,,异或 ,输出Bob正确。
所以标程的cnt % 2肯定不对。结论:你给的标程可能是错误的,或者它是针对另一个题目的。
但既然你要求根据这个标程写题解,我就基于它的逻辑来解析。
六、基于标程的正确结论(假设标程正确)
标程的核心是:
$$g(x) = \begin{cases} 1, & x = 1 \\ \omega(x) \bmod 2, & x > 1 \end{cases} $$其中 是 的不同质因数个数。
那么整个游戏的 SG 值就是所有 的 的异或和,并特判 时为 。
七、算法步骤
- 预处理:用埃氏筛或线性筛计算 以内每个数的最小质因子
spf[x]。 - 计算 :
- 若 ,直接返回 。
- 否则,利用
spf分解 ,统计不同质因数的个数cnt,返回cnt % 2。
- 合并:对于每个测试用例,计算所有 的 Grundy 数的异或和。
- 判断:若异或和非零,输出
"Alice",否则输出"Bob"。
八、复杂度分析
- 筛法:,。
- 每个数分解:,总 。
- 总复杂度满足要求。
九、最终代码(即你给出的标程)
#include <bits/stdc++.h> using namespace std; const int N = 1e7 + 5; int spf[N]; // smallest prime factor void sieve() { for (int i = 2; i < N; ++i) { if (spf[i] == 0) { spf[i] = i; for (int j = i * 2; j < N; j += i) { if (spf[j] == 0) spf[j] = i; } } } } int grundy(int x) { if (x == 1) return 1; int cnt = 0; int last = 0; while (x > 1) { int p = spf[x]; if (p != last) { cnt++; last = p; } x /= p; } return cnt % 2; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); sieve(); int t; cin >> t; while (t--) { int n; cin >> n; int xr = 0; for (int i = 0; i < n; ++i) { int a; cin >> a; xr ^= grundy(a); } cout << (xr ? "Alice" : "Bob") << '\n'; } return 0; }
- 1
信息
- ID
- 7228
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 4
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者