在 OI 中常常有这样一类问题:两个人按照一定的规则轮流进行游戏,最后只能有一个人获胜。给定游戏的初始状态,求先手是否有必胜策略。这类问题我们统称博弈问题。
博弈问题通常有三类解决方法:dp 求解(SG 函数),数据结构维护,数学推导(结论),有时需要综合运用其中的几种方法。
一、一些定义与两个基本定理
定义:
- 游戏的一些参数称为游戏的状态。
- 游戏的初始状态称为初态,最终能判定两人中谁获胜的状态叫做终态。
- 游戏从一个状态能转移到的另一个状态叫做这个状态的后继态。
- 存在先手必胜策略的状态叫做必胜态,不存在先手必胜策略的状态叫做必败态。
两个基本定理:
- 必胜态的所有后继态中存在至少一个必败态。
- 必败态的所有后继态都是必胜态。
这两个定理读者自证不难。
二、dp 求解(SG 函数)
这是解决博弈问题最普适的一类方法。通过刚才的定义和基本定理,我们可以发现博弈游戏其实就是一系列状态的转移,类似 dp 的过程,所以我们有时会使用 dp 来解决博弈问题,或是为数学推导法打表找到一些周期性的规律,进行优化。
例题 1. 为美好的世界献上爆炎(version 1)
传送门:https://www.luogu.com.cn/problem/T467518
桌上有 \(n\) 枚硬币,两⼈轮流拿硬币。每次可以在区间 \([l, r]\) 中选择⼀个数字 \(x\) 然后拿走 \(x\) 枚硬币,若⼀⽅⽆法再拿取则输掉了游戏,由惠惠先⼿开始拿硬币。
惠惠和悠悠都是聪明的,现在惠惠想在游戏开始前,请你帮忙判断她是否能够必胜,若她可以必胜则会按照必胜策略和悠悠进⾏游戏,若不能必胜她就只好作弊来战胜悠悠了。
对于另 \(20\%\) 的数据,满足 \(n \leq 5000, t \leq 5000\)。
题解:运用两个基本定理,我们可以写出这样的递推式:
时间复杂度 \(O(n^2)\)。
vector<int> dp(n + 1, 0); // 0 ... l - 1, 先手必败
for (int i = l; i <= n; i++) {for (int j = l; j <= r && j <= i; j++) {dp[i] |= !dp[i - j]; // 能转移到必败态,就是必胜态}
}
if (dp[n]) {cout << "yes\n";
} else {cout << "no\n";
}
例题 2. [AGC010F] Tree Game
传送门:https://www.luogu.com.cn/problem/AT_agc010_f
题解:我们钦定一开始放的位置为根结点进行 dp。首先考虑儿子都是叶子结点的节点,如果放在这个节点上,一定是先手在根,后手在某个叶子,如此来回,此时是先手掌握主动权。那么如果叶子的最小值比根小,就是必胜态。
再考虑更复杂的树,一定能规约到若干上一种情况。如果子树有先手必胜策略,那么一定不能放后手到那个节点上。否则规约到上一种情况,可以写出递推式:
时间复杂度 \(O(n^2)\)。
这题启发我们,类似 dp 的过程,我们分析博弈问题也可以从基本的子结构开始逐步扩展分析,这是非常实用的方法。
三、数据结构维护
通过数据结构,我们可以写出复杂的博弈递推,并且用数据结构进行优化。
例题 3. 为美好的世界献上爆炎(version 2)
在上一版的基础上,\(\sum n \le 10^5\)。
使用数据结构维护 dp 即可,时间复杂度 \(O(n \log n)\)。这类问题本质是数据结构优化 dp,不多做展开。
例题 4. The Game
传送门:https://codeforces.com/problemset/problem/2062/E2
琪露诺和大妖星正在玩一个游戏,游戏树由 \(n\) 个节点组成,根节点为 \(1\)。第 \(i\) 个节点的值是 \(w_i\)。他们轮流玩游戏,琪露诺先手。
在每一轮中,假设对手在最后一轮中选择了 \(j\) ,玩家可以选择满足 \(w_i > w_j\) 的任何剩余节点 \(i\) ,并删除节点 \(i\) 的子树。特别的,琪露诺可以在第一轮中选择任意节点并删除其子树。第一个不能操作的玩家获胜,他们都希望获胜。
Simple Version:
找到琪露诺在第一轮可能选择的任意一个节点。不存在输出 \(0\)。
题解:设琪露诺选择了节点 \(u\)。那么只要在 \(u\) 的子树外存在一个权值比 \(w_u\) 大的节点 \(v\),那么琪露诺不会直接输掉(这是 \(u\) 要满足的必要条件)。在这些不会输掉的点中选择权值最大的一个即可(因为换到后手后也不能选择直接输掉的点,但是这些点中权值最大的已经被选择了,所以后手就必败了)。
Hard Version:
找到琪露诺在第一轮可能选择的所有节点。不存在输出 \(0\)。
题解:类似 Simple Version
,我们要让后手完全不能进行操作(因为如果后手能进行操作,就能进行 Simple Version
中的构造使得先手输掉)。
设先手选择了节点 \(u\) 让后手无法选择,对于所有后手可能选择的节点 \(v\)(有 \(w_v > w_u\)),必须满足其中一个条件:
- \(v\) 在 \(u\) 的子树内;
- \(v\) 不在 \(u\) 的子树内,但是除了 \(v\) 和 \(u\) 的子树内,不存在任何权值比 \(w_v\) 大的节点 \(x\)。
第一个条件是好进行处理的。对于第二个条件,可以转化为在 \(v\) 的子树外比权值比 \(w_v\) 大的节点 \(x\) 的最近公共祖先 \(l_v\) 在 \(u\) 的子树内(或者干脆不存在这样的节点 \(x\))。
维护 \(l_v\) 的 \(dfn\),配合 ST 表即可。
cin >> n, cnt = 0;
vector<array<int, 2>> node;
for (int i = 1; i <= n; i++) {cin >> a[i], G[i].clear();node.push_back({a[i], i});
}
for (int i = 1; i < n; i++) {cin >> u >> v;G[u].push_back(v);G[v].push_back(u);
}
dfs(1, 0);
vector<int> ans;
LCAFenwick<int> pre(n), suf(n);
MAXFenwick<int> pre_mx(n), suf_mx(n);
MINFenwick<int> pre_mn(n), suf_mn(n);
sort(node.begin(), node.end(), greater<>());
for (int l = 0, r = 0; l < n; l = r) {while (r < n && node[r][0] == node[l][0]) r++;for (int x = l; x < r; x++) { // calculate lcaconst int i = node[x][1];lca[i] = LCA(pre.query(dfn[i] - 1),suf.query(n + 1 - dfn[i] - siz[i]));const int mx = std::max(pre_mx.query(dfn[i] - 1),suf_mx.query(n + 1 - dfn[i] - siz[i]));const int mn = std::min(pre_mn.query(dfn[i] - 1),suf_mn.query(n + 1 - dfn[i] - siz[i]));if (lca[i] == 0) continue;if (dfn[i] <= mn && mx < dfn[i] + siz[i]) {ans.push_back(i);}}for (int x = l; x < r; x++) { // update info.const int i = node[x][1];pre.update(dfn[i], i);suf.update(n + 1 - dfn[i], i);if (lca[i] != 0) {pre_mx.update(dfn[i], dfn[lca[i]]);pre_mn.update(dfn[i], dfn[lca[i]]);suf_mx.update(n + 1 - dfn[i], dfn[lca[i]]);suf_mn.update(n + 1 - dfn[i], dfn[lca[i]]);}}
}
这也启示我们,有的博弈过程是很短暂的,可能只会进行一两轮。
三、数学推导
例题 5. 为美好的世界献上爆炎(version 3)
在上一版的基础上,\(n \le 10^9\)。
题解:容易归纳证明有这样的周期性规律:\(dp_0 \sim dp_{l - 1} = 0, dp_{l} ~ dp_{r} = 1, dp{r + 1} \sim dp_{r + (r - l)} = 1, \dots\),取模判断即可。
例题 6. Resourceful Caterpillar Sequence
传送门:https://codeforces.com/problemset/problem/2053/E
题解:首先观察到后手可以撤销操作,所以游戏最多进行 \(2\) 轮,否则一定平局。
- 如果 \(p\) 和 \(q\) 都是叶子节点,结果为平局。
- 如果 \(p\) 是叶子节点而 \(q\) 不是,Nora 获胜。
- 如果 \(q\) 是叶子节点而 \(p\) 不是,Aron 获胜。
- 如果 \(p\) 和 \(q\) 都不是叶子节点:
- \(k = 1\) 可以吗?当且仅当 \(p\) 与叶子节点相邻时,Nora 获胜。
- \(k = 2\) 可以吗?当且仅当 \(p\) 不与叶子节点相邻,且 \(f(p, q)\) 与叶子节点相邻时,Aron 获胜。
- 否则,结果为平局。
实现即可。