1:博弈模型为两人轮流决策的博弈。并且两人都使用最优策略来取得胜利。
2:博弈是有限的。即无论两人如何决策,都会在有限步决出胜负。
3:博弈是公平的。即两人进行决策的规则相同。
1:bash博弈;2:nim博弈;3:威佐夫博弈;5:Fibonacci博弈;6:sg函数;
假设一堆石子有n个,每次最多取m个,甲乙两个玩家轮流取石子,最后把石子取完的人获胜,保证甲乙每一步的决策都是最优的,请问给定n和m,问甲胜还是乙胜。
不妨假设刚刚开始
n = m + 1
,那么后手必胜,有如下结论:
n = ( m + 1 ) * r + s 其中(r > 1,0 <= s < m + 1)。如果s=0的话,先手每次取k个,后手只要取(m+1-k)个即可,后手必赢。如果s!=0的话,先手者第一次取s个,后手第一次取k个,接下来先手只要取(m + 1 - k)个即可,先手必赢。例题:
hdu_2188
#include<bits/stdc++.h>
using namespace std;
int c, m, n;//总捐款数,每次最多m
int main() {
    //freopen("in.txt","r",stdin);
	cin >> c;
	while (c--) {
		cin >> n >> m;
		if (n % (m + 1) == 0)
			cout << "Rabbit\n";
		else
			cout << "Grass\n";
	}
	return 0;
}
hdu_1846
hdu_1847
假设有n堆石子,每堆石子分别有\(a_1,a_2,…,a_n个\),每次可以选择任意一堆且至少取1枚石子, 甲乙两个玩家轮流取石子, 最后把石子取完的人获胜, 保证甲乙每一步的决策都是最优的, 甲为先手操作, 问甲胜还是乙胜。
当\(a\)不全为\(0\)时, 任意一个\(res!=0\)的局面, 先手可以通过一定的操作让后手面对\(res=0\)的局面。
对于任意一个\(res=0\)的局面, 先手无法通过任何操作让后手面对\(res=0\)的局面。
得出结论, 当\(res=0\)时先手必败, 反之必胜。
问题描述: 有一个\(n\)级台阶的楼梯, 每级台阶上有若干个石子, 其中第i级台阶上有aiai个石子\((i≥1)\)。两位玩家路轮流操作, 每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶上(不能不拿)。
已经拿到地面的石子不能再拿, 最后无法进行操作的人视为失败。
问如果两人都采取最优策略, 先手是否必胜.
1): 考虑极端情况, 当\(a1,a3,…,an\)全为0时, \(res=0\), 此时先手只能将偶数级台阶往下搬, 后手只需要将先手从偶数级台阶上搬下来的石子全部搬到下一级偶数级台阶, 先手必败。
2): 当\(res=x≠0\)时, 通过经典\(Nim\)游戏的证明, 我们知道一定有一种方法搬一定的石子到下一级让后手面对res为0的局面。
3):当\(res=x=0\)且\(a\)不全为\(0\)时, 我们无法通过任何操作让下一个状态的\(res\)也为\(0\)。
即对于\(res\)不为\(0\)的情况, 先手总能通过一定的操作让后手面对\(res\)为\(0\)的情况,。
然而\(res\)为\(0\)时, 先手无论做什么操作都无法让后手面对\(res\)为\(0\)的情况。
那么此刻我们就将题目转化为在奇数台阶上的经典Nim游戏。
思考题:
为什么不用\(res=a_2∧a_4∧a_6∧,…,∧a_n=0\)(n为偶数)来判定胜负?
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 10;
int n, a[maxn], res;
int main() {
    //freopen("in.txt","r",stdin);
	while (cin >> n,n)
	{
		res = 0;
		for (int i = 1; i <= n; i++){
			cin >> a[i];
			res ^= a[i];
		}
		if (res == 0) puts("0");
		else{
			int ans = 0;
			for (int i = 1; i <= n; i++)
				if ((res ^ a[i]) < a[i]) ans++;
			cout << ans << endl;
		}
	}
	return 0;
}
hdu_1730(经典Nim)
#include<bits/stdc++.h>
using namespace std;
int main() {
	//freopen("in.txt","r",stdin);
	int n, m;
	while (cin >> n >> m) {
		int res = 0;
		for (int i = 1; i <= n; ++i) {
			int a, b; cin >> a >> b;
			res = res ^ (abs(a - b) - 1);
		}
		if (res == 0) puts("BAD LUCK!");
		else puts("I WIN!");
	}
	return 0;
}
poj_1704(台阶Nim)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1000 + 10;
int a[N];
int main() {
	//freopen("in.txt", "r", stdin);
	int t, n;
	cin >> t;
	while (t--) {
		int ans = 0;
		cin >> n;
		for (int i = 1; i <= n; ++i) cin >> a[i];
		sort(a + 1, a + n + 1);
		if (n % 2)
		{
			ans ^= (a[1] - 1);
			for (int i = 3; i <= n; i += 2)
				ans ^= (a[i] - a[i - 1] - 1);
		}
		else for (int i = 2; i <= n; i += 2)
			ans ^= (a[i] - a[i - 1] - 1);
		if (ans) printf("Georgia will win\n");
		else printf("Bob will win\n");
	}
return 0;
}
hdu_4315(台阶Nim)
#include<bits/stdc++.h> 
using namespace std;
const int maxn = 1e3 + 10;
int a[maxn];
int n, k;
int main() {
    //freopen("in.txt","r",stdin);
    while(cin >> n >> k)
    {
        memset(a, 0, sizeof a);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        if(k == 1) puts("Alice");
        else
        {
            int res = 0;
            if(n & 1)
            {
                if(k == 2) res ^= a[1] - 1;
                else res ^= a[1];
                for(int i = 3; i <= n; i += 2)
                    res ^= a[i] - a[i - 1] - 1;
            }
            else
            {
                for(int i = 2; i <= n; i += 2)
                    res ^= a[i] - a[i - 1] - 1;
            }
            if(res) puts("Alice");
            else puts("Bob");
        }
    }
    return 0;
}
两堆石子各有若干个, 两人轮流从一堆取至少一个石子或从两堆取同样多的物品, 最后一名取完石子者胜利。
?首先考虑最(zhao)极(gui)端(lv)的情况, (0, 0), (1, 2), (3, 5)局面为先手必败局面。而且这样的数字对被称为奇异局势。
奇异局势的定义如下:
接下来我们看奇异局势的几个性质:
性质1: 任何自然数都包含在一个且仅有一个奇异局势中。
性质2: 任意操作都能将奇异局势转变为非奇异局势.
性质3: 采取适当的方法, 可将非奇异局势转变为奇异局势。
证明略

hdu_2177
#include<bits/stdc++.h> using namespace std;
int n, m;
    
bool check(int n, int m) {
    int x = min(n, m), y = max(n, m);
    double c = (sqrt(5.00000) + 1) / 2;
    double d = (double)(y - x);
    if(x == int(c*d)) return 1; // 必败     return 0;
}
    
void work() {
    if(n > m) swap(n, m); // (n, m)     //第一个模块 我们能一起减去让他成为必败态     {
        int tem = m - n;
        double c = (sqrt(5.00000) + 1) / 2;
        int a = double(tem) * c;
        int b = a + tem;
        if(n - a == m - b) cout << a << " " << b << endl;
    }
    //第二个模块 我们求出当前n的奇异局势, 如果m比他大 拿走就行     //如果m比他小我们求出(x, n) 然后拿走m     {
        double c = (sqrt(5.00000) + 1) / 2;
        int x = n;
        double d = x / c;
        int y = n + d;
        if(m > y) cout << x << " " << y << endl;
        else
        {
            double x = double(n) * 2 / (sqrt(5.000000) + 1);
            cout << int(x) << " " << n << endl;
        }
    }
}
    
int main() {
    while(cin >> n >> m)
    {
        if(!(n + m)) break;
        if(check(n, m)) puts("0");
        else
        {
            puts("1");
            work();
        }
    }
    return 0;
}
    
一堆石子有nn个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完。以后每次取的石子数不能超过上次取子数的22倍。取完者胜。给定nn,问先手必胜还是必败。

例题:
hdu_2516
#include<bits/stdc++.h> using namespace std;
typedef long long ll;
unordered_map<int, int> mp;
ll f[50];
void fib() {
    f[0] = f[1] = 1;
    for(int i = 2; i <= 50; i++)
    {
        f[i] = f[i - 1] + f[i - 2];
        mp[f[i]]++;
    }
}
    
int main() {
    int n;fib();
    while(cin >> n)
    {
        if(n == 0)break;
        if(!mp[n]) puts("First win");
        else puts("Second win"); //如果是fibonacci数, 则先手必败
    return 0;
}
mex运算的结果。s的SG函数值, 即\(SG(G)=SG(s)\)SG函数为0。
hdu_1524
#include<bits/stdc++.h> using namespace std;
const int maxn = 1e3 + 10;
int n, num;
int sg[maxn];
    
int head[maxn], ver[maxn], nex[maxn], tot;
void add(int x, int y) {
    ver[++tot] = y; nex[tot] = head[x]; head[x] = tot;
}
    
int GetSg(int x) {
    if(sg[x] != -1) return sg[x];
    bool vis[maxn];
    memset(vis, 0, sizeof(vis));
    for(int i = head[x]; i; i = nex[i]) // 扫描所有出边     {
        int y = ver[i];
        sg[y] = GetSg(y);
        vis[sg[y]] = 1; //所有出边的sg函数值     }
    for(int i = 0; i < n; i++)
        if(!vis[i]) return sg[x] = i; // mex运算     return 0;
}
    
void init() {
    memset(head, 0, sizeof(head));
    memset(nex, 0, sizeof nex);
    memset(ver, 0, sizeof ver);
    memset(sg, -1, sizeof sg);
    tot = 0;
}
    
int main() {
    while(cin >> n)
    {
        init();
        for(int i = 0; i < n; i++)
        {
            cin >> num;
            while(num--)
            {
                int x; scanf("%d", &x);
                add(i, x);
            }
        }
        while(cin >> num)
        {
            if(!num) break;
            int res = 0;
            while(num--)
            {
                int x; scanf("%d", &x);
                res ^= GetSg(x);
            }
            if(res) puts("WIN");
            else puts("LOSE");
        }
    }
    return 0;
}
hdu_1536
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
int s[maxn], sg[maxn];
int k;
    
void init() {
    memset(sg, -1, sizeof(sg));
}
    
int GetSg(int x) {
    if(sg[x] != -1) return sg[x];
    bool vis[maxn]; memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= k; i++)
        if(x >= s[i])
        {
            sg[x - s[i]] = GetSg(x - s[i]);
            vis[sg[x - s[i]]] = 1;
        }
    for(int i = 0; ; i++)
        if(!vis[i]) return sg[x] = i;
    return 0;
    
}
    
int main() {
    ios::sync_with_stdio(false);
    while(cin >> k)
    {
        init();
        if(k == 0) break;
        for(int i = 1; i <= k; i++) cin >> s[i];
        int num; cin >> num;
        while(num--)
        {
            int x, res = 0; cin >> x;
            for(int i = 1; i <= x; i++)
            {
                int y; cin >> y;
                res ^= GetSg(y);
            }
            if(res) cout << "W";
            else cout << "L";
        }
        cout << endl;
    }
    return 0;
}
原文:https://www.cnblogs.com/RioTian/p/13032919.html