知识点补档1

博弈论

一. 抽象表示

将组合游戏中的每一个状态抽象成图中的一个点,将每一步决策抽象为图中的一条边。这样,对于组合游戏的每一次博弈,我们都可以将其抽象成游戏图中的一条从某一顶点到出度为0的路径

二. P, N状态

一个先手胜状态被认为是一个N-状态(因为下一个玩家即将获胜),一个后手胜状态被认为是一个P-状态(因为前一个玩家即将获胜)。
P-和N-状态归纳性地描述如下:

一个点v是P状态当且仅当它的所有后继为N状态
一个点v是N状态当且仅当它的一些后继为P状态

三. SG函数

给定一个有限子集S ⊂ \subset N,令mexS(最小排斥值)为没有出现在S中的最小自然数
现在给定一个游戏图G = (V, E),则有 g ( v ) = m e x { g ( w ) ∣ ( v , w ) ∈ E } g(v) = mex\lbrace{g(w) | (v, w) {\in} E}\rbrace g(v)=mex{g(w)(v,w)E}
性质:

对于任意的局面,若它的SG值为0,则它任何一个后继局面的SG值不为0
对于任意的局面,若它的SG值不为0,那么它一定有一个后继局面的SG值为0

在我们每次只能进行一步操作的情况下,对于任何的游戏的和,我们若将其中的任一单一 SG-组合游戏换成数目为它的SG 值的一堆石子,该单一 SG-组合游戏的规则变成取石子游戏的规则(可以任意取,甚至取完),则游戏的和的胜负情况不变。

四. Anti-SG游戏

定义:决策集合为空的游戏者赢,即取走最后一个石子的人败
SJ定理:对于任意一个 Anti-SG 游戏,如果我们规定当局面中所有的单一游戏的 SG 值为 0 时,游戏结束,则先手必胜当且仅当:

  • 游戏的 SG 函数不为 0 且游戏中某个单一游戏的 SG 函数大于 1
  • 游戏的 SG 函数为 0 且游戏中没有单一游戏的 SG 函数大于 1。

五. Multi-SG游戏

定义:在符合拓扑原则的前提下,一个单一游戏的后继可以为多个单一游戏,即可以将一堆石子分为多堆石子
先从Multi-Nim开始:有n堆石子,两个人可以从任意一堆石子中拿任意多个石子(不能不拿)或把一堆数量不少于2石子分为两堆不为空的石子,没法拿的人失败。问谁会胜利。这有一神奇定义:

S G ( x ) = { x − 1 , x    m o d    4 = 0 x , x    m o d    4 = 1   o r   2 x + 1 , x    m o d    4 = 3 SG(x) = \begin{cases} x-1, & \text{$x\;mod\;4 = 0$} \\ x, & \text{$x\;mod\;4 = 1\,or\,2$} \\ x + 1, & \text{$x\;mod\;4 = 3$} \end{cases} SG(x)=x1,x,x+1,xmod4=0xmod4=1or2xmod4=3

六. Every-SG游戏

定义:对于还没有结束的单一游戏,游戏者必须对该游戏进行一步决策,即每一个可以移动的棋子都要移动
有如下解法:在通过拓扑关系计算某一个状态点的 SG 函数时(事实上,我们只需要计算该状态点的 SG 值是否为 0)对于 SG 值为 0 的点,我们需要知道最快几步能将游戏带入终止状态,对于 SG 值不为 0 的点,我们需要知道最慢几步游戏会被带入终止状态,我们用step函数来表示这个值。

s t e p ( v ) = { 0 , v 为 终 止 状 态 m a x ( s t e p ( u ) ) + 1 , S G ( v ) > 0 ⋂ u 为 v 的 后 继 状 态 ⋂ S G ( u ) = 0 m i n ( s t e p ( u ) ) + 1 , S G ( v ) = 0 ⋂ u 为 v 的 后 继 状 态 step(v) = \begin{cases} 0, & \text{$v为终止状态$} \\ max(step(u))+1, & \text{$SG(v) > 0 \bigcap u为v的后继状态 \bigcap SG(u) = 0$} \\ min(step(u))+1, & \text{$SG(v) = 0 \bigcap u为v的后继状态$} \end{cases} step(v)=0,max(step(u))+1,min(step(u))+1,v为终止状态SG(v)>0uv的后继状态SG(u)=0SG(v)=0uv的后继状态

定理:对于Every-SG游戏先手必胜当且仅当单一游戏中最大的step为奇数

七. 翻硬币游戏

一般规则如下:

  • N 枚硬币排成一排,有的正面朝上,有的反面朝上。我们从左开始对硬币按 1 到 N 编号。
  • 游戏者根据某些约束翻硬币(如:每次只能翻一或两枚,或者每次只能翻连续的几枚),但他所翻动的硬币中,最右边的必须是从正面翻到反面。
  • 谁不能翻谁输。

结论:局面的SG值为局面中每个正面朝上的棋子单一存在时的SG值的异或和

八. 树的删边游戏

规则如下:

  • 给出一个有 N 个点的树,有一个点作为树的根节点。
  • 游戏者轮流从树中删去边,删去一条边后,不与根节点相连的部分将被移走。
  • 谁无路可走谁输。

定理:叶子节点的SG值为0;中间节点的SG值为它的所有子节点的SG值加1后的异或和

九. 无向图删边游戏

1. Christmas Game

题目大意:

  • 有 N 个局部联通的图。
  • Harry 和 Sally 轮流从图中删边,删去一条边后,不与根节点相连的部分将被移走。Sally 为先手。
  • 图是通过从基础树中加一些边得到的。
  • 所有形成的环保证不共用边,且只与基础树有一个公共点。
  • 谁无路可走谁输

有如下性质:

  • 对于长度为奇数的环,去掉其中任意一个边之后,剩下的两个链长度同奇偶,异或之后的 SG 值不可能为奇数,所以它的 SG 值为 1;
  • 对于长度为偶数的环,去掉其中任意一个边之后,剩下的两个链长度异奇偶,异或之后的 SG 值不可能为 0,所以它的 SG 值为 0;

所以我们可以去掉所有的偶环,将所有的奇环变为长短为1的链,这样就改造成了上一节的模型,算出每一棵树的SG值之后,再按Nim游戏异或即可

2. 无向图删边游戏

对Christmas Game进行一步拓展——去掉对环的限制,规则如下:

  • 一个无相联通图,有一个点作为图的根。
  • 游戏者轮流从图中删去边,删去一条边后,不与根节点相连的部分将被移走。
  • 谁无路可走谁输。

对于这一模型,有一著名定理Fusion Principle :

我们可以对无向图做如下改动:将图中的任意一个偶环缩成一个新点,任意一个奇环缩成一个新点加一个新 边;所有连到原先环上的边全部改为与新点相连。这样的改动不会影响图的 SG 值。

这样,我们可以将任意一个无向图改成树结构,“无向图删边游戏” 就变成了 “树的删边游戏”

十.简单博弈

1.斐波那契博弈

1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍。取完者胜.先取者负输出"Second win".先取者胜输出"First win".

Zeckendorf定理(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和

#include
#include
using namespace std;
 
int main()
{
    int n, fib[50];
    fib[0] = 2; fib[1] = 3;
    for(int i = 2; i < 50; i++)
        fib[i] = fib[i-1] + fib[i-2];
    while(~scanf("%d", &n) && n){
        int flag = 1;
        for(int i = 0; i < 50; i++){
            if(fib[i] == n){
                flag = 0;
                cout << "Second win" << endl;
            }
            if(fib[i] > n)
                break;
        }
        if(flag){
            cout << "First win" << endl;
        }
    }
    return 0;
}

2.威佐夫博弈

有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜

#include 
using namespace std;
 
int main()
{
	int a, b, c;
	while(~scanf("%d %d", &a, &b)){
		if(a > b)
			swap(a, b);
		c = floor(b-a) *((sqrt(5.0)+1)/2);
		if(a == c)	cout << "0" << endl;
		else	cout << "1" << endl;	
	}
	return 0;
}

3.Nimk博弈

有n堆各若干个物品,两个人轮流从K堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜

const int MAXN = 10005;
int SG[MAXN];//需要处理的SG值数组
int XOR[MAXN];//储存每一个二进制位上的和
int xxx;//储存每一个SG值的临时变量
int num;//储存当前SG值有多少位的临时变量
int maxn;//储存最大的SG值位数
 
bool solve(int N,int M)//N表示SG数组的大小,从1到N,M表示每次可以取1到M堆
{
	memset(XOR, 0, sizeof XOR);
	maxn = -1;
	for (int i = 1; i <= N; i++)
	{
    	xxx = SG[i];
		num = 0;
		while (xxx) {
			XOR[num] += xxx&1;
			num++;
			xxx >>= 1;
		}
		maxn = max(maxn, num);
	}
	for (int i = 0; i < maxn; i++)
		if (XOR[i] % (M + 1))
			return true;//返回true表示先手必胜
	return false;//返回false表示先手必败
}

十一.例题

1.HDU–1536

在最简单的取石子情况中,有如下规律:
{ s g ( x ) = x 可 以 取 任 意 个 石 子 s g ( x ) = x    m o d    ( 1 + m ) 可以取1 m个石子 s g 打 表 可 以 取 石 子 个 数 为 不 连 续 整 数 \begin{cases} sg(x) = x & \text{$可以取任意个石子$} \\ sg(x) = x\;mod\;(1+m) & \text{可以取1~m个石子}\\ sg打表 & \text{$可以取石子个数为不连续整数$} \end{cases} sg(x)=xsg(x)=xmod(1+m)sg可以取任意个石子可以取1 m个石子可以取石子个数为不连续整数

#include 
using namespace std;
 
const int N=10008;//N为所有堆最多石子的数量
int f[108],sg[N];//f[]用来保存只能拿多少个,sg[]来保存SG值
bool hash[N];//mex{}
void sg_solve(int t,int N)
{
    int i,j;
    memset(sg,0,sizeof(sg));
    for(i=1;i<=N;i++) {
        memset(hash,0,sizeof(hash));
        for(j=1;j<=t;j++)
            if(i - f[j] >= 0)
               hash[sg[i-f[j]]] = 1;
        for(j=0;j<=N;j++)
            if(!hash[j])
                break;
        sg[i] = j;
    }
}
 
 
int main()
{
    int k, m, l, num;
    while(scanf("%d",&k), k) {
        for(i= 1;i <= k;i++) scanf("%d",&f[i]);
        sg_solve(k, N);
        scanf("%d", &m);
        string ans = "";
        for (int i = 1; i <= m; i++) {
            int sum = 0;
            scanf("%d", &l);
            for (int j = 1; j <= l; j++) {
                scanf("%d", &num);
                sum ^= sg[num];
            }
            if(sum == 0) ans+="L";
            else    ans+="W";
        }
        cout<
2.HDU–1524

博弈的抽象表示,帮助理解SG

#include 
using namespace std;

const int maxn = 1005;
int sg[maxn], pat[maxn];
vector G[maxn];

int getSG(int u)
{
    if (sg[u] != -1)    return sg[u];
    bool mex[10*maxn];
    memset(mex, false, sizeof(mex));
    for (int i = 0; i < G[u].size(); ++i) {
        int v = G[u][i];
        getSG(v);
        mex[sg[v]] = true;
    }
    for (int i = 0; ;++i) {
        if (!mex[i]) {
            sg[u] = i;
            break;
        }
    }
    return sg[u];
}

int main()
{
    int N, m, p, x;
    while (~scanf("%d", &N)) {
        for (int i = 0; i < N; ++i) {
            G[i].clear();
            pat[i] = 0;
        }
        for (int i = 0; i < N; ++i) {
            scanf("%d", &p);
            while (p--) {
                scanf("%d", &x);
                G[i].push_back(x);
                pat[x]++;
            }
        }
        memset(sg, -1, sizeof(sg));
        for (int i = 0; i < N; ++i) {
            if (pat[i] == 0)    getSG(i);
        }
        while (~scanf("%d", &m), m) {
            int ans = 0;
            for (int i = 0; i < m; ++i) {
                scanf("%d", &x);
                getSG(x);
                ans ^= sg[x];
            }
            if (ans != 0)   cout << "WIN" << '\n';
            else    cout << "LOSE" << '\n';
        }
    }
    return 0;
}
3.BZOJ–1299

对于一个博弈游戏而言,重要的是通过操作留给对面一个 s g = 0 sg=0 sg=0的局面

#include
#include
using namespace std;
int n,sg[2001],a[2001];
bool found;
int dfs(int x,int used,int now)
{
     if(x==n+1)
     {
     	   if(!now&&used>0)found=1;
     	   return 0;
     }
     dfs(x+1,used,now);
     dfs(x+1,used+1,now^a[x]);
}
int main()
{
	   for(int t=1;t<=10;t++)
	   {
	   	   memset(sg,-1,sizeof(sg));
					found=0;
	       scanf("%d",&n);
	       for(int i=1;i<=n;i++)
	       	   scanf("%d",&a[i]);
    	   dfs(1,0,0);
    	   if(found)puts("NO");
    	   else puts("YES");
	   }
	   return 0;
}

类Green博弈

现有一棵树,树的每条边都有长度,A和B轮流染色,每次可选择一条边进行染色,染色长度 ≤ \leq 边长,已染色的边不能再染色,同时将子节点删除。

green博弈变形,对于都是1的就是green博弈 S G [ u ] SG[u] SG[u] ^ = S G [ v ] + 1 =SG[v]+1 =SG[v]+1

对于大于1的边,偶数对其没有贡献,奇数有贡献, S G [ u ] SG[u] SG[u] ^$= SG[v] KaTeX parse error: Expected group after '^' at position 1: ^̲(val[v] % 2)$

#include 
using namespace std;

const int maxn = 2005;
int cnt;
struct Node {
    int to, val, next;
}edge[maxn];

int sg[maxn], head[maxn];

void add(int u, int v, int w)
{
    edge[cnt].to = v;
    edge[cnt].val = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs(int u, int fa)
{
    sg[u] = 0;
    for (int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u);
        if (edge[i].val == 1) sg[u] ^= (sg[v]+1);
        else    sg[u] ^= (sg[v] ^ (edge[i].val % 2));
    }
}
int main()
{
    int T;
    cin >> T;
    for (int j = 1; j <= T; ++j) {
        memset(head, -1, sizeof(head));
        cnt = 0;
        int n, u, v, w;
        scanf("%d", &n);
        for (int i = 1; i < n; ++i) {
            scanf("%d %d %d", &u, &v, &w);
            add(u, v, w);
            add(v, u, w);
        }
        dfs(0, 0);
        printf("Case %d: ", j);
        if (sg[0])  cout << "Emily" << '\n';
        else    cout << "Jolly" << '\n';

    }
    return 0;
}

阶梯博弈

n个人随机的站在若干级台阶上,最高一级台阶必须站人,A和B分别移动某个人向上走任意走多级台阶,不能越过更高级台阶上的人,且同一台阶上只能站一人,无法继续移动者输。给出n个人位置,计算先手必胜策略。

#include 
#include 
const int N = 100 + 10;
int a[N],b[N];
int main()
{
    int n = 0,i,j,k,sum = 0;
    while(scanf("%d",&a[n])!=EOF)
        n++;
    for(i=1; i

你可能感兴趣的:(博弈论)