公平组合游戏ICG
(1)有两个玩家,游戏规则对两个玩家公平
(2)游戏状态有限,能走的步数也是有限的
(3)轮流走,当一个玩家不能走时游戏结束
(4)游戏的局势不能区分玩家的身份,例如黑白棋就是不行的
特征:
给定初始局势,指定先手玩家,如果双方都采取最优策略,那么获胜者已经确定了,也就是说ICG问题存在必胜策略。
常见ICG裸题:
巴什游戏
尼姆游戏
图游戏和SG函数
威佐夫游戏
Nim游戏:
有n堆石子,第i堆石子的数量是 a i a_i ai, Alice和Bob可以从任意一堆石子中拿走任意石子。Alice先拿,双方都走最优策略,谁最终会胜利?
结论:
a 1 a_1 a1 ^ a 2 a_2 a2 ^ . . . ... ... ^ a n a_n an == 0 − − > --> −−> 先手必败
a 1 a_1 a1 ^ a 2 a_2 a2 ^ . . . ... ... ^ a n a_n an != 0 − − > --> −−> 先手必胜
证明:
首先,0^0^…^0=0,为先手必败。
情况1: a 1 a_1 a1 ^ a 2 a_2 a2 ^ . . . ... ... ^ a n a_n an == 0
情况2: a 1 a_1 a1 ^ a 2 a_2 a2 ^ . . . ... ... ^ a n a_n an == x(x!=0)
情况1在任意一个 a i a_i ai改变后,必然会转移到情况2。、
情况2一定可以转移到情况1:
对于x的最高位的1,一定在 a i a_i ai中找到一个在同样的位为1数 a k a_k ak。并且由于 a k a_k ak 和x的最高位都是1,所以 a k a_k ak ^ x < a k a_k ak。于是可以拿走 ( a k − a k (a_k-a_k (ak−ak ^x)个石子,使得 a k a_k ak变成 a k a_k ak^x。
a 1 a_1 a1 ^ a 2 a_2 a2 ^ . . . ... ... ^ a n a_n an ^x=x^x=0
所以一定可以由条件2转移到条件1。
结论
由于条件1在经过任意操作后都会变成条件2,条件2在经过一定变换一定能变成条件1。依次操作后,首先到条件2的玩家一定获得胜利。所以直接判断初始条件即可。
acwing题库:Nim游戏
#include<iostream>
using namespace std;
int main(){
int n;
cin>>n;
int sum=0;
while(n--){
int k;
cin>>k;
sum^=k;
}
if(sum)puts("Yes");
else puts("No");
return 0;
}
SG函数
Nim游戏博弈给出了Nim游戏的一般解法。但是题目技巧性太强,约束条件较少,不适合进行一般解的推广。所以通过SG函数来进行博弈游戏一般性的推广。
定义运算:
sex(x):返回不属于集合x的最小正整数。
SG(x)={ sex( y 1 y_1 y1) , sex( y 2 y_2 y2) , …,sex( y n y_n yn) }
y是x在经过一次操作后可以转移到的状态。
结论:
S G ( a 1 ) SG(a_1) SG(a1) ^ S G ( a 2 ) SG(a_2) SG(a2) ^ . . . ... ... ^ S G ( a n ) SG(a_n) SG(an) == 0 − − > --> −−> 先手必败
S G ( a 1 ) SG(a_1) SG(a1) ^ S G ( a 2 ) SG(a_2) SG(a2) ^ . . . ... ... ^ S G ( a n ) SG(a_n) SG(an) != 0 − − > --> −−> 先手必胜
证明同Nim函数。
acwing题库:集合-Nim游戏
#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;
const int N = 105,M = 10005;
int k,s[N],n,f[M];
int sg(int x){
if(f[x]!=-1)return f[x];//记忆递归减少运算
set<int > se;
for(int i=0;i<k;i++){
if(s[i]<=x){
se.insert(sg(x-s[i]));
}
}
for(int i=0;;i++){
//相当于sex函数
if(se.count(i)==0){
return f[x]=i;
}
}
}
int main(){
memset(f,-1,sizeof f);
cin>>k;
for(int i=0;i<k;i++){
cin>>s[i];
}
int res=0;
cin>>n;
for(int i=0;i<n;i++){
int h;
cin>>h;
res^=sg(h);
}
if(res==0) puts("No");
else puts("Yes");
return 0;
}