博弈论

博弈论

Nim 游戏:

模板链接

给定 \(n\) 堆石子,两个人,每人每次任取一堆石子的若干个,谁取不到谁输。

先手必胜策略:

算出每堆石子个数的异或和 \(p\),从中选出一堆石子 \(i\),使其从 \(a[i]\) 变为 \(a[i]\oplus p\)(只要一堆石子在 \(p\) 二进制最高位上是一或更高位有一,就可以做到,同时因为异或出了 \(p\),这样的数是必定存在的),使总异或和变为 \(0\),之后对手必定将异或和改变,继续将其变为零,直到有一次是将其变为所有数都是 \(0\)(该状态异或和也为零)。异或和为 \(0\) 先手必败。

阶梯 Nim 游戏:

每个阶梯上放若干个石子,每次一个人任取一个阶梯上的石子,将其放到下一级台阶上,最后没有石子可以移动的人输。

做法:奇数台阶上的 Nim 游戏,赢方每次按照策略将石子移动到下一级台阶,可视为将其拿走,败方如果移动偶数位置的石子至奇数位置,将其原数移动到下一级偶数阶梯即可。

SG 函数:

先手必败局面 SG 值为零。

如果一个状态的 SG 值大于 \(0\),则此局面为必胜局面,否则为必败局面。

SG 的两个运算规则:

\(mex(S)\) 得到不属于集合 \(S\) 的最小正整数。

如果由 \(x\) 局面可到 \(a\),\(b\)....\(h\) 局面,则:

\(SG(x)=mex(SG(a),SG(b)....SG(h))\)

说明:如果之后有必败点,则 \(x\) 的 SG 值一定大于 \(0\),反之则等于 \(0\)。

如果由 \(x\) 局面可到 \(a\),\(b\)....\(h\) 局面,且可同时在这几个局面中行动,及每次可任选一个局面行动,则:

\(SG(x)=SG(a)\oplus SG(b)\oplus ....\oplus SG(h)\)

说明:类似于 Nim 游戏,SG 为零意味着必败局面,等同于这一堆没有石子可拿,SG 大于零则它可以到达 SG 值小于它的任何任何值,相当于可以取任何数量的石子。

结合规则二对规则一取 \(mex\) 说明:小于 SG(x) 的就是先手一定能任意到达的局面,如果先手要走大于 SG(x) 的局面,后手一定也可以回到 SG(x) 的局面,直至先手无法到大于 SG(x) 的局面。

例题:

一: 剪纸游戏

题意:

有一张 \(n*m\) 大的纸,每人切一刀,谁先剪出 \(1\times 1\) 的纸谁赢。问先手是否必胜。

做法:

\(1\times 1\) 是必胜局面,但我们需要找到的是必败局面再将其 SG 值设为零。观察思考发现(懒得写原因了),必败局面有 \(2\times 3\),\(3\times 2\),\(2\times 2\)。\(1\times x\) 是必胜局面,因此走到此局面必败,枚举时直接跳过即可。
(对于严格的 SG 运算,不可直接设必胜局面为 \(1\)。)

code:

#include<bits/stdc++.h>
using namespace std;
int sg[205][205];
int fsg(int x,int y)
{
	if(sg[x][y]!=-1) return sg[x][y];
	bool a[2005]={};
	for(int i=2;i*2<=x;i++) a[fsg(i,y)^fsg(x-i,y)]=1;
	for(int i=2;i*2<=y;i++) a[fsg(x,i)^fsg(x,y-i)]=1;
	int p=0;
	while(a[p]) p++;
	return sg[x][y]=sg[y][x]=p;
}
int main()
{
	memset(sg,-1,sizeof(sg));
	sg[2][2]=sg[2][3]=sg[3][2]=0;
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	fsg(n,m)?puts("WIN"):puts("LOSE");
}

二:魔法珠

做法: SG函数。

三:格鲁吉亚和鲍勃

做法:略。

四:取石子

题意:

有若干堆石子,有两个人,两种操作:

1 拿走其中一堆一个石子。

2 将两堆石子合并。

无法操作的人输,问先手是否必胜。

做法:

难点在于局面的设计。

结论:当没有堆数为一的石子堆时,操作数(石子总数+石子堆数-1)为奇数时先手必胜,为偶数先手必败。

证明:显然正常情况下操作数为奇数先手必败,为偶数先手必败,但变数在于拿掉一堆数量为一的石子堆时,相当于既拿走又合并两次操作,如果没有数量为一的石子堆,每当败者将一堆石子拿到一,胜者将其合并即可。

因此设 \(f[x][y]\) 表示有 \(x\) 堆只有一堆的石子,剩下有 \(y\) 步操作的局面的 SG 值。本题没有用到 SG 的操作二,只关心 SG 值是否为 \(0\),因此末状态不用非要是必败态,当 \(x==0\) 时返回奇偶判断即可。

code:

#include<bits/stdc++.h>
using namespace std;
int f[55][50005];
int sg(int x,int y)
{
	if(f[x][y]!=-1) return f[x][y];
	int flag=0;
	if(x==0) return y&1;
	if(y==1) return sg(x+1,0);
	if(x&&!sg(x-1,y)) flag=1;
	if(x&&y&&!sg(x-1,y+1)) flag=1;
	if(y&&!sg(x,y-1)) flag=1;
	if(x>=2&&!sg(x-2,y+2+(y?1:0))) flag=1;
	return f[x][y]=flag;
	
}
int main()
{
	memset(f,-1,sizeof(f));
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		int a=0,b=-1;
		scanf("%d",&n);
		int x;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&x);
			if(x==1) a++;
			else b+=x+1;
		}
		if(b==-1) b=0;
		puts(sg(a,b)?"YES":"NO");
	}
 } 

五:[ZJOI2009]取石子游戏

神仙烧脑题,是博弈也是 dp。

上一篇:SG函数


下一篇:第1章 自定义界面-基本代码