博弈问题~~


在这里插入图片描述

巴什博奕

在这里插入图片描述
若n%(m+1)==0,则后手赢,反之先手赢。

巴什博奕扩展

在这里插入图片描述
我们发现当石子数量为6的整数倍,无法被一次拿完,同时1~5都是能拿的
所以我们有结论,若n%6==0,则后手赢,反之先手赢。

#include<bits/stdc++.h>
using namespace std;
int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        if(n%6==0)cout<<"Roy wins!\n";
        else cout<<"October wins!\n";
    }
    return 0;
}

Nim博弈

在这里插入图片描述
可以证明若当前异或值不为0,通过一次操作一定可以使其为0;若当前为零,无论如何操作,都会导致异或值不为0
当所有石头堆异或不为0,则先手赢

反Nim博弈

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int main() {
    int t;
    cin >> t;
    while (t--) {
        ios::sync_with_stdio(false);
        cin.tie(0);
        int n;
        cin >> n;
        int x = 0;
        bool flag = 0;
        while (n--) {
            int y;
            cin >> y;
            if (y != 1)flag = 1;
            x ^= y;
        }
        if (flag) {
            if (!x)cout << "Brother\n";
            else cout << "John\n";
        } else {
            if (!x)cout << "John\n";
            else cout << "Brother\n";
        }
    }
    return 0;
}

斐波那契博弈

在这里插入图片描述
首先有个结论,若n为斐波那契数,则先手必须全拿走才能赢,证明见左程云老师的视频
齐肯多夫(Zeckendorf)定理表示任何正整数都可以表示成若干个互不相邻的斐波那契数(不包括第一个斐波那契数)之和。
对于非斐波那契数n,拆分方式为找寻小于n的最大斐波那契数,n减去它,重复操作,得到拆分的斐波那契数,且先手至少拿走的数量为拆分出的斐波那契数的最小值

#include<bits/stdc++.h>
using namespace std;
#define N 1000000000000000
long long a[101];
int f(long long x, int r) {
    int l = 1;
    while (l + 1 < r) {
        int mid = (r + l) >> 1;
        if (a[mid] <= x)l = mid;
        else r = mid;
    }
    return l;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    long long n;
    cin >> n;
    a[0] = 1, a[1] = 1;
    int size=1;
    while(a[size]<=n){
        a[size+1]=a[size]+a[size-1];
        size++;
    }
    int m = size-1;
    if (a[m] == n) {
        cout << n << "\n";
        return 0;
    }
    n -= a[m];
    int ans = a[m];
    while (n > 0) {
        m = f(n, m);
        n -= a[m];
        ans = a[m];
    }
    cout << ans << "\n";
    return 0;
}

威佐夫博弈

在这里插入图片描述

SG函数和SG定理

在这里插入图片描述
举例:Nim博弈

#include <bits/stdc++.h>
using namespace std;
vector<int> a;
string nim(vector<int>& a){
    int max1=0;
    for(auto &num:a){
        max1=max(max1,num);
    }
    int sg[max1+1];//记录sg值
    sg[0]=0;
    bool appear[max1+1];
    for(int i=1;i<=max1;i++){
        memset(appear,0,sizeof appear);//出现数组重置
        //遍历后继节点
        for(int j=0;j<i;j++){
            appear[sg[j]]=true;
        }
        //搜寻未出现的最小自然数设为sg[i]
        for(int s=0;s<=i;s++){
            if(!appear[s]){
                sg[i]=s;
                break;
            }
        }
    }
    //sg定理
    int eor=0;
    for(int &num:a){
        eor^=sg[num];
    }
    return eor!=0 ? "先手" : "后手"; 
}

两堆石头的巴什博奕

在这里插入图片描述
由之前的知识:sg[i]=i%(m+1)
因为sg[a]^sg[b]!=0为必胜态,所以若a%(m+1)!= b%(m+1),先手赢;

三堆石头拿斐波那契数博弈

在这里插入图片描述
不是很难。

E&D 游戏

在这里插入图片描述
通过sg找规律

#include <bits/stdc++.h>
using namespace std;
#define N 9
vector<int> a(4);
int sg[100][100];
void f() {
    bool appear[10000];
    for (int i = 2; i <= 100; i++) {
        for (int j = 1; j <= i; j++) {
            memset(appear, 0, sizeof appear);
            for (int k = 1; k <= j / 2; k++) {
                appear[sg[j - k][k]] = 1;
            }
            for (int k = 1; k <= i / 2; k++) {
                appear[sg[i - k][k]] = 1;
            }
            for (int k = 0; k <= i; k++) {
                if (!appear[k]) {
                    sg[i][j] = k;
                    break;
                }
            }
        }
    }
}
int main() {
    sg[1][1] = 0;
    f();
    cout<<"       ";
    for(int i=1;i<=N;i++)cout<<(i)<<" ";
    cout<<"\n\n\n";
    for(int i=1;i<=N;i++){
        cout<<(i)<<"      ";
        for(int j=1;j<i;j++){
            cout<<"x ";
        }
        for(int j=i;j<=N;j++){
            cout<<sg[j][i]<<" ";
        }
        cout<<'\n';
    }
    cout<<"将行列数减一"<<'\n';
    cout<<"       ";
    for(int i=1;i<=N;i++)cout<<(i-1)<<" ";
    cout<<"\n\n\n";
    for(int i=1;i<=N;i++){
        cout<<(i-1)<<"      ";
        for(int j=1;j<i;j++){
            cout<<"x ";
        }
        for(int j=i;j<=N;j++){
            cout<<sg[j][i]<<" ";
        }
        cout<<'\n';
    }
    return 0;
}

在这里插入图片描述

  • 通过离谱的找规律发现sg[a][b]=(a-1)|(b-1)的二进制表示中最低位0所在的位置

从而得到最终代码

#include <bits/stdc++.h>
using namespace std;
//计算sg[a][b]
int f(long long a,long long b){
    int cnt=0;
    long long x=(a-1)|(b-1);
    while(x&1){
        cnt++;
        x>>=1;
    }
    return cnt;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t,n;
    long long a,b;
    cin>>t;
    while(t--){
        cin>>n;
        int eor=0;
        for(int i=0;i<n/2;i++){
            cin>>a>>b;
            eor^=f(a,b);
        }
        if(eor)cout<<"YES\n";
        else cout<<"NO\n";   
    }
    return 0;
}

分裂游戏

在这里插入图片描述

  • 将每个糖豆看成独立的游戏,设其所在的位置为i,则该游戏为sg[i];
  • 考虑sg[i]的求解方法,遍历所有可能分解情况,例如分解到j,k位置,则考虑sg[j]^sg[k],sg[i]不能等于上述值。最后找到最小的可选自然数。
#include <bits/stdc++.h>
using namespace std;
int sg[23];
bool appear[400];
int main(){
	//sg函数,将瓶子编号倒一下,方便处理
    sg[0]=0;
    sg[1]=1;
    for(int i=2;i<23;i++){
        memset(appear,0,sizeof appear);
        appear[0]=1;
        for(int j=i-1;j>=0;j--){
            for(int k=j-1;k>=0;k--){
                appear[sg[j]^sg[k]]=1;
            }
        }
        for(int m=0;m<400;m++){
            if(!appear[m]){
                sg[i]=m;
                break;
            }
        }
    }
    int t,n,x;
    cin>>t;
    while(t--){
        cin>>n;
        //sg定理
        int eor=0;
        for(int i=0;i<n;i++){
            cin>>x;
            if(x&1)eor^=sg[n-1-i];
        }
        if(eor==0){
            cout<<"-1 -1 -1\n0\n";
        }else{
        	//求第一步的方案数
            int cnt=0;
            int flag=1;
            for(int i=n-1;i>=0;i--){
                for(int j=i-1;j>=0;j--){
                    for(int k=j;k>=0;k--){
                        if((eor^sg[i]^sg[j]^sg[k])==0)cnt++;//将sg[i]去掉,将sg[j]和sg[k]加上,eor就变成将他们异或
                        if(cnt==1 && flag)cout<<(n-1-i)<<' '<<(n-1-j)<<' '<<(n-1-k)<<'\n',flag=0;
                    }
                }
            }
            cout<<cnt<<'\n';
        }
    }
    return 0;
}

妙不可言妙不可言~~~~

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值