前言
从今天开始复习和整理下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;
}