博弈论
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。