【acm 博弈论 】 之 Nim游戏与sg函数

前言

从今天开始复习和整理下acm的部分模块,从博弈论开始。
著名的“取石子”游戏通常有3种类型,从简单到复杂依次是:
1.巴什博弈
2.威佐夫博弈
3.Nim游戏
4.Nim游戏与sg函数
复习Nim博弈之前,先复习下前两者。


巴什博弈

大意:

一堆物品有n个,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜,问最优策略下谁赢。

思路:

不难发现,当剩下m+1个物品时,先手必败。

所以,如果当前的物品个数是(m+1)的倍数,无论先手如何取,后手都总能把剩下的物品数变成(m+1)的倍数 ,后手必胜。 反之,如果当前的物品个数不是(m+1)的倍数,那么先手总能将剩下的物品个数变成(m+1)的倍数,先手必胜。

结论:

当 n % (m+1) ==0 时 ,后手必胜;反之,先手必胜。


威佐夫博弈

大意:

有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。

题目:
洛谷P2252
https://www.luogu.com.cn/problem/P2252

思路:
同上,找出必败态,根据必败态寻找规律,不再赘述。

结论:
设两堆石头分别有a,b个,其差值为d。

其必败态为: d *[ (sqrt(5) +1) / 2 ] == min(a,b) .

其中(sqrt(5) +1) / 2 是黄金分割(好神奇)。


Nim游戏

大意:
有n堆石子,第i堆石子有ai个。每次可以从一堆石子中取任意多个,取到最后一颗石子的人获胜,问最优策略下谁赢。

思路:
网上太多了,就不再赘述了。采用二进制进行异或的方法是真的很妙。。。

结论:
sum = a1^ a2 ^a3 ^a4 ^ … … ^an ; ( ^ 即异或)
sum == 0 ,后手胜;
sum == 1 ,先手胜;


Nim游戏与sg函数

虽然Nim游戏可以采用直接求异或和的方法获得答案,但我们很容易发现,Nim游戏其实只是这一类组合游戏中的一种极特殊的情况。倘若我们对Nim游戏中的取石子规则稍作修改,例如:
只能取斐波那契数;
只能取质数;
只能取某些指定的数字;
这时就必须引入sg函数进行处理。

sg函数即对于当前的局面i:
sg[i] = mex{可以由当前局面转移到的每一个局面的sg值}
其中mex{} 运算是取最小的不属于该集合的自然数
例如,
最终态是局面0,则sg[0] = 0。

由局面1可以且仅可以转移到局面0,
则sg[1] = mex{sg[0]} = mex{0} = 1;

由局面2可以且仅可以转移到局面0,1,
则sg[2] = mex{sg[0],sg[1]} = mex{0,1} = 2;

由局面3可以且仅可以转移到局面0,2,
则sg[3] = mex{sg[0],sg[2]} = mex{0,2} = 1;

sg函数为Nim游戏提供了一个很好的解题模型,那就是:
把原游戏分解成多个独立的子游戏,
则原游戏的SG函数值是它的所有子游戏的SG函数值的异或。

来看下面这道例题:

题目

在这里插入图片描述

题意

还是Alice和Bob的游戏,
给一个仅由大写英文字母构成的字符串(length<=40) ,
每一次可以删去相同的一个,两个或者全部的字母。
删掉最后一个字母的人将获胜,问最优策略下谁赢?

样例

在这里插入图片描述

思路

显然,我们把相同的字母放在一个堆中,这就是Nim游戏的一个简单变形。
仅仅把 “从一个堆中任意取出石子 ” 变成 “从一个堆中取出1,2 或 全部 石子”。

我们需要一个sg[i][j] 来记录第i堆石子在剩下j枚石子时的sg值。
其中 sg[i][j] = mex{ sg[i][0],sg[i][j-2],sg[i][j-1]} ,采用递推实现。
最后将每一堆的sg[i][size] 求异或和就好了。具体见代码。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 50;
int box[maxn];
int sg[maxn][maxn];  //sg[i][j] 第i堆石子剩下j枚石子时的sg值 
vector<int> v;     //存放每堆石子的个数 
char s[maxn];      

void get_sg(){
	int size = v.size();
	for(int i=0;i<size;i++){
		sg[i][0] = 0;
		sg[i][1] = 1;
		for(int j=2;j<=v[i];j++){
			int f[3] = {sg[i][0],sg[i][j-1],sg[i][j-2]};  //对于当前sg[i][j]可以转移到的3种状态,-1,-2和清空。
			for(int k=1;k<=3;k++){     //由题意可知,sg[i][j]只可能在0,1,2,3中取值 
				if(k!=f[1]&&k!=f[2]){
					sg[i][j] = k;
					break;
				}
			}
		}
	}
}
int main(){
	cin>>s;
	int len = strlen(s);
	for(int i=0;i<len;i++){
		int now = s[i] - 'A';
		box[now]++;
	}
	for(int i=0;i<maxn;i++){
		if(box[i]!=0)
		v.push_back(box[i]);
	}
	 
	get_sg();
	int ans = 0;
	int size = v.size();
	for(int i=0;i<size;i++){
		ans ^= sg[i][v[i]];
	} 
	if(ans) 
	cout<<"Alice"<<endl;
    else 
	cout<<"Bob"<<endl;
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值