[算法]博弈论

[算法]博弈论

一、有向图游戏

所谓有向图游戏,就是在一个有向无环图中,只有一个起点,上面有一个棋子,两个玩家轮流沿着有向边推动棋子,不能走的玩家判负。可以看出,这个游戏必须会终止。
定义必败状态为先手必败状态,必胜状态为先手必胜状态。
我们可以得到下面三条定理:
定理一:没有后继状态的状态必是必败状态。
定理二:必胜状态必有某个后继状态为必败状态。
定理三:必败状态的后继状态如果有,那么必然全是必胜状态。

二、SG函数

定义 m e x mex mex函数为不属于集合 S S S中的最小非负整数。例如
m e x ( { 0 , 2 , 4 } ) = 1 , m e x ( ϕ ) = 0 mex\left(\{ 0,2,4\} \right)=1,mex(\phi)=0 mex({0,2,4})=1,mex(ϕ)=0
对于状态 x x x以及其所有 k k k个后继状态 y 1 , y 2 , … , y k y_1,y_2,\dots,y_k y1,y2,,yk,定义SG函数:
S G ( x ) = m e x ( S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) ) SG(x) = mex(SG(y_1),SG(y_2),\dots,SG(y_k)) SG(x)=mex(SG(y1),SG(y2),,SG(yk))
对于终止状态 x x x,定义 S G ( x ) = 0 SG(x)=0 SG(x)=0.
而对于由 n n n个有向图游戏组成的组合游戏,设它们的起点分别为 s 1 , s 2 , … , s n s_1,s_2,\dots,s_n s1,s2,,sn,则有定理:当且仅当 S G ( s 1 ) ⨁ S G ( s 2 ) ⨁ ⋯ ⨁ S G ( s n ) ≠ 0 SG(s_1)\bigoplus SG(s_2)\bigoplus \dots \bigoplus SG(s_n)\ne 0 SG(s1)SG(s2)SG(sn)=0时,这个游戏是先手必胜的。

k = S G ( s 1 ) ⨁ S G ( s 2 ) ⨁ ⋯ ⨁ S G ( s n ) k=SG(s_1)\bigoplus SG(s_2)\bigoplus \dots \bigoplus SG(s_n) k=SG(s1)SG(s2)SG(sn)
我们证明,当 k = 0 k=0 k=0时为必败状态, k ≠ 0 k\ne 0 k=0时为必胜状态。
对于定理一,显然有
0 ⨁ 0 ⨁ ⋯ ⨁ 0 = 0 0\bigoplus 0\bigoplus \dots \bigoplus 0= 0 000=0
对于定理二, S G ( s 1 ) ⨁ S G ( s 2 ) ⋯ S G ( s n ) ≠ 0 SG(s_1)\bigoplus SG(s_2)\cdots SG(s_n)\ne 0 SG(s1)SG(s2)SG(sn)=0的时候,必存在某种操作,使得改变某个 a i a_i ai a i ′ a_i' ai后,必有 a 1 ⨁ a 2 ⨁ ⋯ ⨁ a i ⋯ ⨁ a n = 0 a_1\bigoplus a_2 \bigoplus \dots \bigoplus a_i \dots \bigoplus a_n = 0 a1a2aian=0
根据异或定义,可得 a i ′ = a i ⨁ k a_i'=a_i\bigoplus k ai=aik,那么必有奇数个 a i a_i ai k k k的二进制最高位为 1 1 1。我们取出这些 a i a_i ai中的每一个,然后我们可以发现必有 a i > a i ⨁ k a_i>a_i\bigoplus k ai>aik,因而这是个合法的移动。
对于定理三,可以发现如果这个操作合法,那么必有 a i = a i ′ a_i=a_i' ai=ai,矛盾!

三、Nim游戏

n n n堆物品,每堆有 a i a_i ai个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。
取走最后一个物品的人获胜。
我们发现每堆石子是独立的,然后 S G ( a i ) = a i SG(a_i)=a_i SG(ai)=ai(用数学归纳法不难得出),于是我们只需要求出
a 1 ⨁ a 2 ⨁ ⋯ ⨁ a n a_1\bigoplus a_2 \bigoplus \dots \bigoplus a_n a1a2an的值即可。

四、拓展Nim游戏

集合-Nim游戏

每次可以拿走的石子的个数只能是集合 { s 1 , s 2 , … , s k } \{s_1,s_2,\dots,s_k \} {s1,s2,,sk}中的某个数。
我们直接记忆化搜索即可,实现 m e x mex mex函数的时候注意用 s e t set set判重。

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;

const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 110;

int n,k;
int s[maxn],h[maxn];
int f[10010];

inline int sg(int x){
	if(f[x] != -1) return f[x];
	unordered_set<int> tmp;//子节点的sg函数值可能会有重复,所以用集合去重
	for(int i = 1; i <= k && x >= s[i]; i++){
		tmp.insert(sg(x-s[i]));
	}
	for(int i = 0;; i++) if(!tmp.count(i)) return f[x] = i;
}

void solve(){
	memset(f, -1, sizeof f);
	scanf("%d",&k);
	for(int i = 1; i <= k; i++) scanf("%d",&s[i]);
    sort(s+1,s+k+1);
    for(int i = 1 ; i <= k; i++) cout<<s[i]<<endl;
	scanf("%d",&n);
	int res = 0;
	for(int i = 1; i <= n; i++){
		scanf("%d",&h[i]);
		res ^= sg(h[i]);
	}
	if(res) puts("Yes");
	else puts("No");
}

int main()
{
	solve();
	return 0;
}

拆分-Nim游戏

每次把一堆全部拿走,然后替换成规模更小的两堆(最小规模为0)。
显然这个游戏会终止,并且是个有向图游戏。
对于状态 x x x,它的后继状态是两个堆,并且互相独立,所以我们可以求出两个堆的 S G SG SG值,然后异或起来。

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;

const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 1e6 + 10;

int n,x;
int f[110];

inline int sg(int x){
	if(f[x] != -1) return f[x];
	unordered_set<int> tmp;
	for(int i = 0; i < x; i++)
		for(int j = 0; j < x; j++)
		tmp.insert(sg(i)^sg(j));
	for(int i = 0; ;i++)
		if(!tmp.count(i)) return f[x] = i;
}

void solve(){
	scanf("%d",&n);
	int ans = 0;
	memset(f,-1,sizeof f);
	for(int i = 1; i <= n; i++){
		scanf("%d",&x);
		ans ^= sg(x);
	}
	if(ans) puts("Yes");
	else puts("No");
}

int main()
{
	solve();
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值