最优博弈:Nim游戏,SG函数

公平组合游戏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 (akak ^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;
}

猜你喜欢

转载自blog.csdn.net/qq_45931661/article/details/119990624