博弈论——公平组合游戏与SG函数

参考自算法竞赛进阶指南

公平组合游戏与有向无环图

一个博弈游戏被称为公平组合游戏(ICG)当且仅当其同时满足3个条件

  1. 由两名玩家交替行动
  2. 游戏任意时刻可进行的操作与轮到哪名玩家无关
  3. 不能进行操作的玩家判负

任何一个公平组合游戏都可以用一个有向无环图游戏模型表示
这个有向无环图有唯一的起点,表示游戏初始局面
图中每个结点表示一个局面,有向边表示能从一个局面到达另一个局面
想象初始时起点有一颗棋子,两名玩家轮流沿有向边将棋子移动一步,若某名玩家操作时棋子所在结点已无出边则判负
即分别代表了ICG中玩家交替行动和不能进行操作的玩家判负

SG函数

在有向无环图游戏模型中,对于每个节点 x x x,设其后继节点为 y 1 , y 2 , . . . , y k y_1,y_2,...,y_k y1,y2,...,yk
定义 S G ( x ) = m e x ( S G ( y 1 ) , S G ( y 2 ) , . . . , S G ( y n ) ) SG(x)=mex(SG(y_1),SG(y_2),...,SG(y_n)) SG(x)=mex(SG(y1),SG(y2),...,SG(yn))
其中 m e x ( S ) = m i n x ∈ N , x ∉ S { x } mex(S)=min_{x \in N, x \notin S }\{x\} mex(S)=minxN,x/S{x}
m e x ( S ) mex(S) mex(S)等于不属于集合S最小非负整数

规定起点为s的有向图游戏G有 S G ( G ) = S G ( s ) SG(G)=SG(s) SG(G)=SG(s)

G 1 , G 2 , . . . , G k G_1,G_2,...,G_k G1,G2,...,Gk为k个有向图游戏
若有向图游戏 G G G的操作规则为任选某个有向图游戏 G i G_i Gi并在 G i G_i Gi上行动一次
G G G被称为 G 1 , G 2 , . . . , G k G_1,G_2,...,G_k G1,G2,...,Gk的和,有
S G ( G ) = S G ( G 1 )   x o r   S G ( G 2 )   x o r   . . .   x o r   S G ( G n ) SG(G)=SG(G_1)\ xor\ SG(G_2)\ xor\ ...\ xor\ SG(G_n) SG(G)=SG(G1) xor SG(G2) xor ... xor SG(Gn)

ICG与SG函数的关系

规定有向图游戏中没有出边的结点 x x x S G ( x ) = 0 SG(x)=0 SG(x)=0,则有

有向图游戏某个局面 x x x必胜,当且仅当 S G ( x ) > 0 SG(x)>0 SG(x)>0
有向图游戏某个局面 x x x必负,当且仅当 S G ( x ) = 0 SG(x)=0 SG(x)=0

一个简单的证明如下:
若结点x的某后继节点y有SG(y)=0,则mex运算后一定有SG(x)>0
即处于局面x的玩家只要向局面y移动,则一定能必胜

若结点x的后继节点均有SG(y)>0,则mex运算后一定有SG(x)=0
即处于局面x的玩家无论如何操作都一定会到达必败局面

SG函数的应用

HDU - 1847 Good Luck in CET-4 Everybody!

题目大意:初始有n张卡,两人轮流取走2的次幂(1,2,4,8…)张牌,不能取的人判负,两人均采取最优策略,求先手是否必胜
题目分析:可以直接套用上述SG函数公式, S G ( n ) = m e x ( { S G ( n − k )   ∣   k ≤ n   A n d   k = 1 , 2 , 4 , 8 , 16 , . . . } ) SG(n)=mex(\{SG(n-k)\ |\ k \leq n\ And\ k=1,2,4,8,16,...\}) SG(n)=mex({SG(nk)  kn And k=1,2,4,8,16,...}),边界为 S G ( 0 ) = 0 SG(0)=0 SG(0)=0

#include<iostream>
#include<cstdlib> 
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

int SG[1010];

int getSG(int n)
{
	if(SG[n]!=-1) return SG[n];
	
	int check[1010];
	memset(check,0,sizeof(check));
	
	for(int k=1;k<=n;k<<=1)
	check[getSG(n-k)]=1;
	
	for(int i=0;i<1010;++i)
	if(!check[i]){
		return SG[n]=i;
	}
}

int main()
{	
	memset(SG, -1, sizeof(SG));
	SG[0]=0;
	
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		if(getSG(n)==0) printf("Cici\n");
		else printf("Kiki\n");
	}
	return 0;
}

HDU - 1848 Fibonacci again and again

题目大意:有三堆石子,分别有n、m、p个石子,两人轮流任选一个石堆取斐波那契数个石子,先取完者胜,求先手是否必胜
题目分析
假设只有一对石子,那么思路和上题一致
S G ( n ) = m e x ( { S G ( n − f i b o [ i ] )   ∣   f i b o [ i ] ≤ n } ) SG(n)=mex(\{SG(n-fibo[i])\ |\ fibo[i] \leq n\}) SG(n)=mex({SG(nfibo[i])  fibo[i]n}),边界为 S G ( 0 ) = 0 SG(0)=0 SG(0)=0
当有三堆石子时,整个游戏就是三个取石子游戏的和,即 a n s = S G [ n ]   x o r   S G ( m )   x o r   S G ( p ) ans=SG[n]\ xor\ SG(m)\ xor\ SG(p) ans=SG[n] xor SG(m) xor SG(p)


HDU - 3980 Paint Chain

题目大意:有一根含有n个珠子的,初始时珠子都无颜色,两人轮流给连续的m个无色珠子上色,无法上色者判负,两人均采取最优策略,求先手是否必胜
题目分析
注意到无论第一次上色怎么选,都会把剩下可上色的部分变成一个n-m的首尾不相交的链
这样问题可以转化成给n-m的链上色的游戏
一个长度为n的链上色游戏可以转化成 i 和 n-m-i 两个链上色游戏的和
即有 S G ( n ) = m e x ( { S G ( i )   x o r   S G ( n − m − i )   ∣   0 ≤ i ≤ n − m } ) SG(n)=mex(\{SG(i)\ xor\ SG(n-m-i)\ |\ 0\leq i \leq n-m\}) SG(n)=mex({SG(i) xor SG(nmi)  0inm})
边界条件为 S G ( n ) = 0   ( n < m ) SG(n)=0\ (n<m) SG(n)=0 (n<m)

#include<iostream>
#include<cstdlib> 
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

int SG[2010];

int getSG(int n,int m)
{
	if(SG[n]!=-1) return SG[n];
	if(n<m) return SG[n]=0;
	if(n==m) return SG[n]=1; 
	
	int check[5010];
	memset(check, 0, sizeof(check));
	
	check[getSG(n-m, m)]=1;
	
	for(int i=1;i<=n-m-1;++i){
		check[getSG(i,m)^getSG(n-m-i,m)]=1;
	}
	
	int mex=0;
	for(int i=0;i<5010;++i)
	if(!check[i]){
		mex=i;
		break;
	}
	
	return SG[n]=mex;
}

int main()
{	
	int T=read();
	for(int cs=1;cs<=T;++cs)
	{
		memset(SG, -1, sizeof(SG));
		int n=read(),m=read();
		
		if(n<m){
			printf("Case #%d: abcdxyzk\n", cs);
			continue;
		}
		else if(n==m){
			printf("Case #%d: aekdycoin\n", cs);
			continue;
		}
		
		if(getSG(n-m,m)==0) printf("Case #%d: aekdycoin\n", cs);
		else printf("Case #%d: abcdxyzk\n", cs);
	}
	return 0;
}


POJ2311 Cutting Game

题目大意:有一张 w * h 的网格纸,每次操作可选择任意一张网格纸,沿格线水平或垂直将纸剪成两张,先剪出 1 * 1 的玩家获胜,在两名玩家均采取最优策略的情况下,先手是否必胜?

题目分析
首先将该游戏转化为有向图游戏
显然一张 n ∗ m n*m nm网格纸的游戏可以分解为两个 i ∗ m i * m im 网格纸和 ( n − i ) ∗ m (n-i)*m (ni)m 网格纸的新游戏的和
所以 S G ( n , m ) = m e x ( { S G ( i , m )   x o r   S G ( n − i , m ) ∣ 2 ≤ i ≤ n − 2 } ⋃ { S G ( n , i )   x o r   S G ( n , m − i ) ∣ 2 ≤ i ≤ m − 2 } ) SG(n,m)=mex(\{SG(i,m)\ xor\ SG(n-i,m)|2\leq i \leq n-2\} \bigcup \{SG(n,i)\ xor\ SG(n,m-i)|2\leq i \leq m-2\}) SG(n,m)=mex({SG(i,m) xor SG(ni,m)2in2}{SG(n,i) xor SG(n,mi)2im2})

边界条件 S G ( 2 , 2 ) = S G ( 2 , 3 ) = S G ( 3 , 2 ) = 0 SG(2,2)=SG(2,3)=SG(3,2)=0 SG(2,2)=SG(2,3)=SG(3,2)=0
因为2x2,2x3,3x2的网格纸无论怎么剪都会产生1*x的纸,使得对方一定可以剪出1 * 1

#include<iostream>
#include<cstdlib> 
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

int SG[210][210];

int getSG(int n, int m)
{
	if(SG[n][m]!=-1) return SG[n][m];
	
	int check[250];
	memset(check, 0, sizeof(check));
	
	for(int i=2;i<=n-i;++i)
	{
		int sg=getSG(i,m)^getSG(n-i,m); 
		check[sg]=1;
	}
	
	for(int i=2;i<=m-i;++i)
	{
		int sg=getSG(n,i)^getSG(n,m-i); 
		check[sg]=1;
	}
	
	int mex=0;
	for(int i=0;i<250;++i)
	if(!check[i]){
		mex = i;
		break;
	}
	
	return SG[n][m]=mex;
}

int main()
{	
	memset(SG, -1, sizeof(SG));
	SG[2][2]=SG[2][3]=SG[3][2]=0;
	
	int w,h;
	while(scanf("%d%d",&w,&h)!=EOF)
	{
		if(getSG(w,h)==0) printf("LOSE\n");
		else printf("WIN\n");
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值