博弈论相关问题

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;
}
posted @ 2021-06-22 20:10  andysj  阅读(163)  评论(0编辑  收藏  举报