1 条题解
-
0
题目大意
这是一个基于公平组合游戏的博弈论问题。游戏规则如下:
- 给定一个阈值 ,有 组游戏
- 每组游戏有 堆石子,每堆 个
- 玩家轮流操作,每次选择一堆石子(数量 )和一个整数
- 将这堆石子分成 堆,要求分得尽量平均(最多一堆比最少一堆多1)
- 无法操作(所有堆石子数 )的玩家输掉游戏
- 小A的对手先手,小A后手
需要判断每组游戏的胜者,输出 1 表示先手胜,0 表示后手胜。
算法思路
1. 博弈论基础
这是一个多堆公平组合游戏,可以使用 Sprague–Grundy 定理 解决:
- 整个游戏的 SG 值 = 每堆石子 SG 值的异或和
- 异或和 ⇒ 先手必胜
- 异或和 ⇒ 后手必胜
2. 单堆 SG 函数计算
定义 为一堆数量为 的石子的 Grundy 值:
- 如果 ,不能操作,
- 如果 ,枚举所有可能的操作
分堆方式
对于 分成 堆:
- 分堆结果: 堆有 个石子, 堆有 个石子
后继状态 SG 值
分堆后的 SG 值为:
$$SG_{\text{new}} = [r \bmod 2 \cdot SG(q+1)] \oplus [(M-r) \bmod 2 \cdot SG(q)] $$3. 优化技巧
数论分块
直接枚举 会超时,利用数论分块优化:
- 对于固定的 , 的取值范围是连续的区间
- 在每个区间内,只需检查 和 即可覆盖所有可能的奇偶组合
- 将复杂度从 降至
内存优化
- SG 值通常不会很大,使用固定大小的
visited
数组(大小1000足够) - 避免频繁的
memset
大数组
代码实现
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int MAXN = 100005; const int MAX_SG = 1000; int F, T; int SG[MAXN]; bool visited[MAX_SG]; vector<int> results; int main() { scanf("%d%d", &T, &F); for (int x = 0; x < MAXN; x++) { if (x < F) { SG[x] = 0; continue; } memset(visited, 0, sizeof(visited)); for (int M = 2; M <= x; M++) { int q = x / M; int r = x % M; int R = x / q; int sg = 0; if (r % 2) sg ^= SG[q + 1]; if ((M - r) % 2) sg ^= SG[q]; if (sg < MAX_SG) visited[sg] = true; if (M + 1 <= R) { r = x - (M + 1) * q; sg = 0; if (r % 2) sg ^= SG[q + 1]; if ((M + 1 - r) % 2) sg ^= SG[q]; if (sg < MAX_SG) visited[sg] = true; } M = R; } int mex = 0; while (mex < MAX_SG && visited[mex]) mex++; SG[x] = mex; } for (int t = 0; t < T; t++) { int N; scanf("%d", &N); int total = 0; for (int i = 0; i < N; i++) { int a; scanf("%d", &a); total ^= SG[a]; } results.push_back(total ? 1 : 0); } for (size_t i = 0; i < results.size(); i++) { printf("%d", results[i]); if (i < results.size() - 1) printf(" "); } printf("\n"); return 0; }
复杂度分析
- 预处理:每个 的处理复杂度为 ,总复杂度 ,在 数据范围内可接受
- 查询:每组游戏
- 空间:
- 1
信息
- ID
- 3492
- 时间
- 1000ms
- 内存
- 64MiB
- 难度
- 10
- 标签
- 递交数
- 5
- 已通过
- 1
- 上传者