博弈论入门

基本概念

  • 先手必胜状:先手可以从这个状态走到某一个必败态。
  • 先手必败态:先手走不到任何一个必败态。
  • 也就是说先手必胜态,那么先手一定能采取某些操作,让后手面对必败态。如果是先手必败态,无论先手怎么操作,都无法让后手面对必败态。

基本模型

巴什博弈

假设一堆石子有n个,每次最多取m个,甲乙两个玩家轮流取石子,最后把石子取完的人获胜,保证甲乙每一步的决策都是最优的,请问给定n和m,问甲胜还是乙胜。

假设, (n = m + 1)​,那么后手必胜。

(n = (m + 1) imes r + s)​, 如果 (s = 0),先手每次取 (x) 个,后手只要每次 ((m + 1 - x)) 个即可,后手必胜,

如果 (s) 不等于 (0), 先手第一次取 (s) 个,后手每次取 (x) 个,先手只要每次 ((m + 1 - x)) 个即可,先手必胜,

所以只需要讨论 (s) 是否为 (0)

nim游戏

玩家: 2人;

道具: (n) 堆石子,每堆石子的数量分别为 (x_1, x_2, …… x_n)

规则:

  1. 游戏双方轮流取石子;
  2. 每人每次选一堆石子,并从中取走若干颗石子(至少取 (1) 颗);
  3. 所有石子被取完,则游戏结束;
  4. 如果轮到某人取时已没有石子可取,那此人算负。

假如两个游戏玩家都非常聪明,问谁胜谁负?

结论:

(x_1 oplus x_2 …… oplus x_n = 0) 则先手必败,反之必胜。

证明:

先考虑它的子问题。

(n = 1)​ 时,显然是先手必胜的,可以理解为 $S_1 $​ ^ (0 = S)

(n = 2)​时,假设一堆与另一堆相等,无论先手取什么,后手跟着取就好了,是后手必胜,可以理解为

(s_1) ^ (s_2 = 0)​​。

假设一堆与另一堆不相等,先手就直接在多的一堆取两堆的差值,这样又变成了上一种情况,不过现在是先手必胜,可以理解为 (s_1)​ ^ (s_2 = s)​​。

接下来将堆数,扩展到 (n)

最终获胜的式子为 $ 0(​​ ^ 0 ^ 0 ^ 0 = 0, 而最初的状态为)s_1$​ ^ $ s_2$​ ^ (s_3) ^ (s_4= s)​。

…………………………

/*
Date:
Source:
konwledge:
*/
#include <iostream>
#include <cstdio>
#define orz cout << "AK IOI" << "
"

using namespace std;
const int maxn = 1e4 + 10;

inline int read()
{
	int f = 0, x = 0; char ch = getchar();
	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
inline void print(int X)
{
	if(X < 0) X = ~(X - 1), putchar('-');
	if(X > 9) print(X / 10);
	putchar(X % 10 ^ '0');
}
int T, n;
int main()
{
	//freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
	T = read();
	while(T--)
	{
		n = read();
		int res = 0;
		for(int i = 1; i <= n; i++) 
		{
			int x = read();
			res ^= x;
		}
		if(res == 0) puts("No");
		else puts("Yes");
	}
	return 0;
}

威佐夫博弈

有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。

结论:

当两堆石子各有 (n)​ 和 (m)​​ 个,设 (n < m)​。

那么先手必败,当且仅当 ((m - n) imes frac {sqrt5 + 1}2 = n)​。

好的,我并不会证明。

/*
Date:
Source:
konwledge:
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#define orz cout << "AK IOI" << "
"
#define int long long

using namespace std;

inline int read()
{
	int f = 0, x = 0; char ch = getchar();
	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
inline void print(int X)
{
	if(X < 0) X = ~(X - 1), putchar('-');
	if(X > 9) print(X / 10);
	putchar(X % 10 + '0');
}
int a, b;
signed main()
{
    a = read(), b = read();
    if(a > b) swap(a, b); 
    int c = b - a;
    int d = (sqrt(5) + 1) / 2 * c;
    if(a == d) puts("0");
	else puts("1");
    return 0;
}

SG函数

mex 运算

定义(mes(s)) 为不属于集合 (s)​​ 的最小非负整数解。

SG 函数

设对于每个节点 (x)​, 设从 (x)​ 出发有 (k)​ 条有向边分别到达节点(y_1,y_2,…,y_k)​ 。

定义 (SG(x))​ 函数为后继节点 (y_1,y_2,…,y_k)​的SG函数值构成的集合再执行 mex运算的结果。

特别的, 整个有向图的 (SG)​ 函数被定义为有向图起点 (s)(SG) 函数值, 即(SG(G)=SG(s))

有向图终点的 (SG) 函数为 (0)

结论:

先手必败, 则该局面对应 (SG)​ 函数 (=0)​,反之必胜。

//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    //因为SG[0]始终等于0,所以i从1开始
    for(i = 1; i <= n; i++){
        //每一次都要将上一状态 的 后继集合 重置
        memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
            S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
        for(j = 0;; j++) if(!S[j]){   //查询当前后继状态SG值中最小的非零值
            SG[i] = j;
            break;
        }
    }
}

挖个坑吧!!!

原文地址:https://www.cnblogs.com/yangchengcheng/p/15243309.html