博弈论相关问题
SG函数
适用范围
- 两人、轮流操作
- 信息公开透明
- 没有随机因素
- 有限步内必然结束
- 不存在平局
- 决策集合为空的游戏者输(即不能操作者输)
- 可以将每个局面中的元素单独分析,元素之间不会有依赖关系(即一次可以选两个元素等)
策梅洛定理:对于这样的一个游戏,任何一个局面先手或者后手其中之一必然存在必胜策略
性质
- 对于当前的局面\(X\),定义其\(SG\)函数值为\(SG(X)\)。其中,先手必胜当且仅当\(SG(X)>0\),反之后手必胜
- 若\(X=X_1+X_2+...+X_n\),则\(SG(X)=SG(X_1)\oplus SG(X_2)\oplus \ ... \oplus \ SG(X_n)\)。特别地,这里的\(a+b\)指的是局面上的组合(可理解为并集)而非数量加和。即,\(a+b=(a,b)\)
- \(SG(X)=mex \{ SG(Y)|X\rightarrow{Y}\}\)(\(X\rightarrow{Y}\)表示\(X\)可以通过一步操作变换到\(Y\))。从而一次博弈过程可以看成一棵有向无环树。那么结束节点为\(SG(\varnothing)=0\)(根据\(mex\)定义)
应用(ABC206F)
注意到覆盖值域只有\([1,99]\cap{N^+}\),则初始局面\(S=(a_1,a_2,a_3,...,a_{99})\)(\(a_i\)表示在当前局面中,第\(i\)个位置还没有被覆盖过。)
注意到覆盖或没覆盖过的地方一定是一段连续的位置。则我们用\([l,r]\)来表示\(a_l\sim{a_r}\),即\(S=[1,99]\)
用记忆化搜索来求解\(SG(S)\)。设当前局面为\(X=[l,r]\),若\(l>r\),\(SG(X)=0\)
否则考虑\(\forall{SG(Y)}|X\rightarrow{Y}\)如何得来。在\(n\)个区间中,我们可以选择一个区间\([p,q]\)满足\([p,q]\subseteq{[l,r]}\)并将其覆盖。此时,\(Y=[l,p-1]\cup{[q+1,r]}\),那么\(SG(Y)=SG([l,p-1])\oplus{SG([q+1,r])}\),可以递归求解。得到\(\forall{SG(Y)}\)后,就可以通过\(SG(X)=mex \{ SG(Y)|X\rightarrow{Y}\}\)求出\(SG(X)\)了。
code
int dfs(int l, int r)
{
if (l > r) return 0;
if (sg[l][r] != -1) return sg[l][r];
int bk[105]; memset(bk, 0, sizeof(bk));
for (int i = l; i <= r; i ++ )
{
for (int o = 0; o < (int)p[i].size(); o ++ )
{
int j = p[i][o];
if (j > r) continue;
bk[dfs(l, i - 1) ^ dfs(j + 1, r)] = 1;
}
}
for (int i = 0; ; i ++ ) if (!bk[i]) return sg[l][r] = i;
}
int main()
{
t = read();
while (t -- )
{
for (int i = 1; i <= 99; i ++ ) p[i].clear();
n = read();
for (int i = 1; i <= n; i ++ )
{
int x = read(), y = read();
p[x].pb(y - 1);
}
memset(sg, -1, sizeof(sg));
if (dfs(1, 99)) puts("Alice");
else puts("Bob");
}
return 0;
}
Anti-SG函数
适用范围
- 决策集合为空的游戏者赢(即不能操作者赢,也可以理解为操作最后一步者输)
- 其他同SG函数
性质:SJ定理
设初始状态为\(S=(a_1,a_2,...,a_n)\),则先手必胜当且仅当下列两种情况的任意一种成立:(下面\(SG\)函数的定义没有改变)
- \(SG(S)>0\land\exists{SG(a_i)>1}\)
- \(SG(S)=0\land{\forall{SG(a_i)\leq1}}\)
Nim博弈
Description
两个人玩取石子游戏:地上有\(n\)堆石子(每堆石子数量小于\(10^4\)),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。求是否存在先手必胜的策略。
Solution
设这\(n\)堆石子分别为\(a_1,a_2,...,a_n\)。则先手必胜当且仅当\(\bigoplus\limits_{i=1}^{n}{a_i}>0\),反之后手必胜
简要证明:\(SG(a_1,a_2,...,a_n)=SG(a_1)\oplus SG(a_2)\oplus \ ... \oplus \ SG(a_n)\),
又\(SG(X)=mex \{ SG(Y)|X\rightarrow{Y}\}=mex\{0,1,2,...,X-1\}=X\)(每次可以取\(1\sim{X-1}\)个石头),
故\(SG(a_1,a_2,...,a_n)=\bigoplus\limits_{i=1}^{n}{a_i}>0\)时先手必胜,反之后手必胜
阶梯Nim问题
Description
有\(𝑛\)个位置,每个位置上有\(𝑎_𝑖\)个石子。两个人轮流操作,步骤是:挑选\(1...𝑛\)中任意一个存在石子的位置\(𝑖\),将至少\(1\)个石子移动至\(𝑖−1\)位置(则最后所有石子都堆在在\(0\)这个位置),谁不能操作谁输。求先手必胜还是必败。
Solution
结论:该问题相当于所有奇数位置的石子做Nim博弈。
证明:假设两个人都只会移动奇数位置的石子(并移到偶数位置上去),这相当于所有奇数位置的石子做Nim博弈(移到偶数位置,等价于在奇数位置取石子,因为偶数位置的石子无论怎样都不会造成影响)
这样可以求出是先手必胜/后手必胜。不妨令先手必胜,则先手一定能通过只移动奇数位置上的石子获胜。因为若后手也只移动奇数位置上的石子,先手必胜;若后手某一步移动了偶数位置的石子,先手可以紧接着把被移动的石子再移动到下一个偶数位置。这样,所有奇数位置的石子数量没有变,只有偶数位置的改变了,但这是没有影响的,故新问题与原问题等价。
应用(P2575)
注意到一次只能对一行操作,而整个棋盘相当于这\(n\)行的组合。所以对每一行算出\(SG\)值并异或,即可判断。
因为某一行的石子之间是会相互影响的,不好直接算\(SG\)函数,考虑模型转化。
阶梯Nim问题要考虑不变量来划分阶段。注意到空格的数目是不变的。于是我们想到把每个空格右边有多少个连续的石子,作为该位置的石子数。这样就转化成了阶梯Nim问题。
code
#include <bits/stdc++.h>
using namespace std;
int t, n, v[25];
int read()
{
int x = 0, fl = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
return x * fl;
}
int main()
{
t = read();
while (t -- )
{
n = read(); int sg = 0;
while (n -- )
{
int m = read(), sg0 = 0, s = 20 - m + 1, tt = 0; memset(v, 0, sizeof(v));
while (m -- ) { int x = read(); v[x] = 1;}
for (int i = 1; i <= 20; i ++ )
{
if (v[i]) {tt ++ ; continue;}
s -- ; if (s & 1) sg0 ^= tt; tt = 0;
}
sg ^= sg0;
}
if (sg) puts("YES"); else puts("NO");
}
return 0;
}
威佐夫博弈
Description
有两堆石子,由两个人轮流取。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子(都至少要取一个)。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,求是否存在先手必胜的策略。
Solution
本题不满足\(SG\)函数的性质,故要换一种方式求解。
设这两堆石子分别有\(x,y\)个且\(x<y\),那么后手必胜当且仅当\(\lfloor\dfrac{\sqrt{5}+1}{2}(y-x)\rfloor=x\)
不会证,但是学习了\(Beatty\)定理:
对于两个无理数\(x,y\),若其满足\(\dfrac{1}{x}+\dfrac{1}{y}=1\),令两个集合\(P=\left\{ p|p=\lfloor nx\rfloor,n \in N^+ \right\},Q=\left\{ q|q=\lfloor my\rfloor,m \in N^+ \right\}\),其中\(N^+\)为正整数集。
则有\(P \cap Q=\varnothing,P \cup Q=N^+\)。
用dp解决博弈问题
Description
有时博弈问题不是某种基本模型,且不能用\(SG\)函数求解,这时可以考虑\(dp\)(通常是要最大化/最小化答案)
Solution
抓住“当前这个人(能得到的)最大/最小值,等于总和减去该人进行这步操作后,另一人的操作值(所有可能情况里的最大/最小,因为另一人也想使答案最优)”
应用(P2964)
令\(f[i][j]\)为当前剩下第\(i\)至第\(n\)个硬币,当前这个人从第\(i\)个硬币开始取\(j\)个硬币所能获得的最大价值。
由于取完后就是另一个人取,他肯定会取价值最大的方案,因此可以列出状态转移方程:
\(f[i][j]=s[n]-s[i-1]-\max\limits_{k=1}^{min(2j,n-i-j+1)}f[i+j][k]\)
其中\(s[i]=\sum\limits_{j=1}^{i}{c[i]}\)
通过维护\(g[i][j]=\max\limits^{j}_ {k=1}f[i][k]\),将时间复杂度优化到\(O(n^2)\)
code
#include <bits/stdc++.h>
using namespace std;
int n, s[2005], f, g[2005][2005];
int read()
{
int x = 0, fl = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
return x * fl;
}
int main()
{
n = read(); for (int i = 1; i <= n; i ++ ) {int x = read(); s[i] = s[i - 1] + x;}
for (int i = n; i >= 1; i -- )
{
for (int j = 1; j <= n - i + 1; j ++ )
{
f = s[n] - s[i - 1] - g[i + j][min(j << 1, n - i - j + 1)];
g[i][j] = max(g[i][j - 1], f);
}
}
printf("%d\n", g[1][2]);
return 0;
}