暑期集训 博弈论概览

暑期集训 博弈论概览

tags: 暑期集训 第一周 博弈论


(一)Bash Game -- 巴什博弈

简介

只有一堆n个物品,两个人轮流从这堆物品中取物, 规定每次至少取一个,最多取m个。最后取光者得胜。
这是编程博弈题中最简单的一类,我们知道当先手面对$n=m+1$的情况时,后手必胜。以此为基准向上推演我们可以得到如下结论:

当且仅当先手面对的物品个数 n 满足 $n=k(m+1)$ 时后手必胜。

因为设先手取 x 个物品,后手一定可以取到(m+1-x)个物品使物品总数变为$n=(k-1)(m+1)$。以此类推,可以发现最后一定能变成$n=m+1$的奇异情况。所以当n%(m+1)==0时后手必胜。与此对应,当n%(m+1)==k>0时,先手可以拿走k个物品让对手面对$n=k(m+1)$的必败情况。

例题

1 - 悼念512汶川大地震遇难同胞——选拔志愿者 HDU - 2188

思路:
翻译一下题目:石头总数为$n_{(0<n<10000)}$,没人每次捐款必须为正整数/大于0,且每次最多捐$m_{(1<=m<=10)}$元。所以直接判断n%(m+1)即可
代码:

/*不被允许出现在第一列的井号*/ #include<bits/stdc++.h>
using namespace std;
int main() {
    int m,n;
    int C;
    cin>>C;
    while(C--) {
        cin>>n>>m;
        if(n%(m+1)) cout<<"Grass"<<endl;
        else cout<<"Rabbit"<<endl;
    }
    // system("pause");
    return 0;
}

2 - Good Luck in CET-4 Everybody! HDU - 1847

思路:
首先我们找到奇异情况$n=3$ 这个时候先手必输。
以此往上推,我们得到当n%3==k时,先手总可以取$2^0$或者$2^1$使对方面对n%3=0的情况,且3的倍数一定不是2的幂次。所以最后一定是对方面对$n=3$的情况。
即n%3>0是先手必胜态。
代码:

/*不被允许出现在第一列的井号*/ #include<bits/stdc++.h>
using namespace std; 
int main() {
    int n;
    while(cin>>n) {
        if(n%3==0) cout<<"Cici"<<endl;
        else cout<<"Kiki"<<endl;
    }
    return 0;
}

3 - Codeforces 1162 - E. Thanos Nim $^{*2100}$

思路:
翻译题目:两个人 Alice 和 Bob 以偶数堆的石头 $a[n] (2≤n≤50, 1≤a_i<)$ 进行游戏,每人每轮可从剩余非空的 $\frac{n}{2}$ 堆石头中取出任意大于 1 数量的石头,同一轮中不同堆中取出的石头可以不相同。若轮到某人时非空石头堆不足 $\frac{n}{2}$ 则此人失败。问在给定数据中谁必定获胜。
本题中,谁先将任意一堆或挤兑==几堆石头取空,谁就输了。因为对家接着取空$\frac{n}{2}$堆石头,剩下的石头堆数就不满 $\frac{n}{2}$了。
什么时候先手能保持不败之地呢?设最少的石子堆a[i]的石子数为a,有b堆这样的石子,当b<=n/2的时候,先手可以将另外一半的石子拿走至与前一半石子堆的数量一致( {a1 a2 ... an/2 a/n2+1... an} 变成 {a1 a2 ...an/2 a1 a2.... an/2} ) ,那么接下来无论对方拿走多少石子,我们都可以模仿对方的策略,显然,对方会先拿控一堆石子走到必败态,那么先手就必赢。在考虑一种情况,当b>n/2时,也就是最少的石子堆数量占一半以上,那么无论先手怎么取,后手都可以将剩下的石子堆保持最少的石子堆有一半以上(无论对方怎么取,我们只需要把n/2个石子堆数量保持与最小的一致),那么此时先手是一定会先拿空一堆石子,那么先手必败。综上,也就是判断最少石子堆数量是否有一半以上即可。
代码:

/**/ #include<iostream>
/**/ #include<algorithm>
using namespace std;
int main() {
    int n;int a[55];
    cin>>n;
    for(int i=0;i<n;i++) {
        cin>>a[i];
    }
    sort(a,a+n);
    if(a[0] == a[n/2]) cout<<"Bob"<<endl;
    else cout<<"Alice"<<endl;
    return 0;
}

(二)Nim Game -- 尼姆博弈

简介 来自百度百科

有$n_{(n>=2)}$堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
先给结论

令第 $i$ 堆石子个数为 $a_i$,当且仅当$a_1\oplus a_2\oplus a_3 \oplus ...\oplus a_n=0$时后手必胜

证明:
令$a_1\oplus a_2\oplus a_3 \oplus ...\oplus a_n$为M。
首先,第一个先手必败点就是所有石头堆数目都为0,此时$0\oplus 0\oplus 0\oplus ...\oplus 0=0$。
其次证明两点:

  1. 任何$M!=0$的情况都能通过一次操作变成$M=0$的情况。
  2. 任何$M=0$的情况都无法经过一次操作变成另一个$M=0$的情况。

当$M=k_{(k\neq 0)}$时,设k的二进制表示有$x$位。则一定有至少一个$a_i$,其二进制表示第$x$位同样为1。
则有$a_i \oplus k<a_i$,即$\exists m\gt 0使a_i-m=a_i\oplus k$。
又通过$x\oplus y \oplus y = x$ 可得:$a_1\oplus a_2\oplus a_3 \cdots a_{i-1} \oplus a_{i+1} \cdots a_n=k\oplus a_i$
所以$a_1\oplus a_2\oplus a_3 \cdots a_{i-1} \oplus a_{i+1} \cdots a_n\oplus (a_i-m)=k\oplus a_i\oplus (a_i-m)$
即$a_1\oplus a_2\oplus a_3 \cdots a_{i-1} \oplus a_{i+1} \cdots a_n\oplus (a_i\oplus k)=k\oplus a_i\oplus (a_i\oplus k) = 0$
1得证
当$M=0$时,设$\exists a_i^{'}=a_i-m_{(m>0)}, 使M^{'}=a_1\oplus a_2\cdots a_i^{'}\cdots \oplus a_n=0$
则有$a_1\oplus a_2\cdots a_i^{'}\cdots \oplus a_n=a_1\oplus a_2\cdots a_i\cdots \oplus a_n$
即$a_i^{'}=a_i$,则$m=0$。可知$m=0$非法。所以2得证

例题

1 - Being a Good Boy in Spring Festival HDU - 1850

思路:
直接套公式计算$N_1\oplus N_2 \cdots \oplus N_M$即可
代码:

/**/#include<bits/stdc++.h>
using namespace std;
int main() {
    int M,N[1000];
    int sum;
    int ans;
    while(cin>>M && M!=0) {
        sum = 0;
        ans = 0;
        for(int i=0;i<M;i++) {
            cin>>N[i];
            sum ^= N[i];
        }
        if(sum == 0) cout<<0<<endl;
        else {
            for(int i=0;i<M;i++) {
                if((sum^N[i]) < N[i]) ans++;
            }
            cout<<ans<<endl;
        }
    }
    system("pause");
    return 0;
}

2 - Northcott Game HDU - 1730

思路:

我认为博弈论有一个很重要的点就是操作无效化。通过模仿对手的操作令部分操作无效化。使题目条件变得更简单
对于这道题而言,我们只需考虑两个棋子之间的空隙,将间隙dang'che即可。因为若先手棋子后退了,则后手只需要将自己的棋子平移相同距离即可。所以我们完全可以忽视两边的空间。且我们知道先手不能随便后退,否则后手就有了改变局势的机会。
于是我们计算每行两颗棋子之间的空隙,并计算其异或和便能得出答案
代码:

/**/#include<bits/stdc++.h>
using namespace std;
int main() {
    int n,m;
    int N[1005];
    int a,b;
    int sum=0;
    while(cin>>n>>m) {
        sum=0;
        for(int i=0;i<n;i++) {
            cin>>a>>b;
            N[i] = abs(a-b)-1;
            sum ^= N[i];
        }
        if(!sum)
            cout<<"BAD LUCK!"<<endl;
        else
            cout<<"I WIN!"<<endl;
    }
    system("pause");
    return 0;
} 

Nim游戏变体之阶梯Nim博弈

阶梯Nim博弈
有一n阶楼梯,第$i$阶楼梯上有$a_i$枚硬币。两个人轮流操作。每次操作可将第$i$阶台阶上的$m_{(m>0)}$枚硬币移动到第$i-1$阶台阶。问先手必胜情况。

在本类nim变体中,我们发现,对于一种情况的先手A而言,若A从第$2k$级台阶移动$m$枚硬币到第$2k-1$级台阶,后手B则可紧跟着将这m枚硬币从第$2k-1$级台阶移动到第$2k-2$级台阶。即先手对偶数级台阶进行操作,最后一定是后手将其放到地面。先手永远不可能将偶数级台阶的硬币亲手放到地面去。由此我们可以忽视任何对偶数级台阶的操作,将奇数级台阶的硬币移动到偶数级台阶便视为这些硬币已经被“拿走”。仅考虑奇数级台阶。这个问题就变成了普通Nim问题。我们只需要计算奇数阶硬币数的异或和$a_1 \oplus a_3 \oplus \cdots \oplus a_{n-((n+1){%}1)}$即可。

例题

3 - Georgia and Bob POJ - 1704

思路:
将两个棋子之间的空隙看成石头。左边是楼梯顶右边是楼梯底。这道题就变成了阶梯Nim博弈
代码:

/**/#include<iostream>
/**/#include<cmath>
/**/#include<algorithm>
using namespace std;
int main() {
    int T;
    cin>>T;
    int N;
    int P[10005] = {0};
    int sum = 0;
    while(T--) {
        sum=0;
        cin>>N;
        for(int i=1;i<=N;i++) {
            cin>>P[i];
        }
        sort(P+1,P+N+1);
        int i=1;
        if(N%2) i=0;
        for(i;i<=N;i+=2) {
            sum ^= (abs(P[i]-P[i+1])-1);
        }
        if(sum) cout<<"Georgia will win"<<endl;
        else cout<<"Bob will win"<<endl;
    }
    system("pause");
    return 0;
}

(三)Wythoff's game -- 威佐夫博弈

简介

有两堆石子$m,n(m>0,n>0)$,双方轮流取走一些石子,合法的取法有如下两种:
1)在一堆石子中取走任意多颗;
2)在两堆石子中取走相同多的任意颗;
约定取走最后一颗石子的人为赢家,求必败态(必胜策略)。

相关博客 - Wythoff’s Game (威佐夫博弈)

很喜欢这篇博客,个人认为有很大的提点作用。博主同样也是转的,原博客地址进不去了。


(四)Sprague-Grundy Function -- SG函数

简介 - 百度百科

相关博客1 - 组合游戏 SG函数和SG定理

相关博客2 - 博弈论 SG函数 SG定理


(五)没有错后面这么简洁就是因为我写不下去了=-=

猜你喜欢

转载自www.cnblogs.com/xglync/p/11225290.html