fibgame 解题报告
题外话
赛时蒟蒻 SG 函数推导 2h 未果,看来我博弈论还得练。
题目描述
定义 \(tree(x)\) 为 \(x\) - fib 树,它的递归定义如下:
- 若 \(x = 0\),则 \(tree(0)\) 为空树;
- 若 \(x = 1\),则 \(tree(1)\) 为单独一个节点;
- 若 \(x > 1\),则 \(tree(x)\) 是一个根节点连接两个子树,分别为 \(tree(x−1)\) 和 \(tree(x−2)\)。
小 Z 和小 W 在树上玩游戏,小 Z 先手。每次玩家可以选择一个节点 \(x\) ,然后将以 \(x\) 为根的子树全删去。规定取到根节点的人输。
下面给出了 \(tree(1)\) ∼ \(tree(6)\) 的图例,其中红点表示小 Z 第一步的必胜点。

现在小 Z 想让你帮他数一数:\(tree(n)\) 中有多少个节点是他第一步的必胜点?
对于 \(100\%\) 的数据,\(1\le n \le 10000\)。
解题思路
显然这道题类似「公平组合游戏」。因此根据套路先考虑求 SG 函数。
策略分析
「公平组合游戏」的定义如下:
- 游戏有两个人参与,二者轮流做出决策,双方均知道游戏的完整信息;
- 任意一个游戏者在某一确定状态可以作出的决策集合只与当前的状态有关,而与游戏者无关;
- 游戏中的同一个状态不可能多次抵达,游戏以玩家无法行动为结束,且游戏一定会在有限步后以非平局结束。
在这道题中,「玩家无法行动」指的是「整棵树只剩下了根节点」。
SG 函数
定义 \(SG(i)\) 表示局面为 \(tree(i)\) 时的 SG 函数值。显然 \(SG(1)=0,SG(2)=1\)。注意到可以在任何时刻将子树删光,但根不能删(否则会输),所以 \(SG(u) \not= SG(u-1)\oplus SG(u-2)\) 。以下简称「形状为 \(x\) 的子树」为子树 \(x\)。
考虑定义 \(f(i)\) 表示 \(SG(u)\) 从儿子 \(i(i\in\{u-1,u-2\})\) 转移时的值。如果删除全部子树 \(i\),则应当转移 \(0\);若 \(SG(i)=0\),则根据已知结论 \(f(i)=f(1)=SG(2)=1\);若 \(SG(i)=1\),则说明 \(f(i)\) 从两个地方转移:
- 全部删除子树 \(i\)
- 删除 \(i\) 的子树 \(p\)
因为 \(i\) 的内部只能有 \(SG(p)=0\),所以根据上一种情况,\(f(p)=1\),那么 \(f(i)=mex\{0,1\}=2\)。以此类推,若 \(SG(i)=x\),则根据归纳,有 \(f(i)=x+1\)。
因此,设节点 \(u(u\ge3)\),则有 \(SG(u)=(SG(u-1)+1)\oplus(SG(u-2)+1)\) 。
点数计算
胜利点的数量计算有多种方式。这里采用 std 的实现。
设 \(dp_{i,j}\) 表示满足「形状为 \(tree(i)\) 的树中,删除子树 \(u\) 后 \(SG(i')=j\)」的 \(u\) 的数量。因为删除必胜点后 \(SG(n')=0\)(即对手必败),所以答案为 \(dp_{n,0}\)。
因为 \(u\) 可以在左子树,也可以在右子树,所以 \(dp_{i,j}\) 的转移方程应当形如 \(d_{i-1,x}+d_{i-2,y}\)。因为两侧子树的 \(f\) 异或值为 \(j\),有 \((x+1)\oplus f(i-2)=(y+1)\oplus f(i-1)=j\),即 \(x=(j\oplus f(i-2))-1\),\(y=(j\oplus f(i-1))-1\)。
综上所述,\(dp_{i,j}=dp_{i-1,(j\oplus (SG(i-2)+1))-1}+dp_{i-2,(j\oplus (SG(i-1)+1))-1}\)。
打表发现 \(SG(x) \le 8190\)。设 \(m=\max\limits_{x=1}^{n} \{SG(x)\}\),则时间复杂度为 \(O(nm)\) 可以通过这道题。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int n, sg[10010], dp[10010][10010];
bitset<10010> vis[10010];
int pd(int i, int j) {
if (vis[i][j]) return dp[i][j];
if (i < 2) return 0;
if (i == 2 && j != 0) return 0;
vis[i][j] = 1;
return dp[i][j] = (((j == sg[i - 2] + 1) ? 1 : pd(i - 1, (j ^ (sg[i - 2] + 1)) - 1)) + ((j == sg[i - 1] + 1) ? 1 : pd(i - 2, (j ^ (sg[i - 1] + 1)) - 1))) % mod;
}
int main() {
#ifndef CWKAPN
freopen("fibgame.in", "r", stdin);
freopen("fibgame.out", "w", stdout);
#endif
scanf("%d", &n);
if (n == 1) {
printf("0\n");
return 0;
}
if (n == 2) {
printf("1\n");
return 0;
}
sg[1] = 0, sg[2] = 1;
for (int i = 3; i <= n; i++) sg[i] = (sg[i - 1] + 1) ^ (sg[i - 2] + 1);
vis[2][0] = 1; dp[2][0] = 1;
printf("%d\n", pd(n, 0));
return 0;
}
本文来自博客园,作者:cwkapn,转载请注明原文链接:https://www.cnblogs.com/cwkapn/p/18721915


浙公网安备 33010602011771号