博弈论基础
学习笔记,内容包含:巴什博弈、威佐夫博弈、必胜必败分析、SG 函数、组合博弈游戏、Nim 类型石子游戏、相关例题
概述#
博弈论,可以定义为两人或多人面对一定的规则约束下,根据所掌握的信息,同时或先后,一次或多次地,从各自允许的行为中选择最优策略,通常要使自身收益最大化。
囚徒困境#
两名罪犯A, B分开囚禁在两个房间内,互相之间不能交流。
每名罪犯可以检举对方犯罪,也可以保持沉默。
- 若A检举B,且B沉默,则B被判20年。
- 若B检举A,且A沉默,则A被判20年。
- 若A,B都检举对方,同时被判8年。
- 若A,B都沉默,同时被判1年。
A/B | A检举 | A沉默 |
---|---|---|
B检举 | 8/8 | 10/0 |
B沉默 | 0/10 | 1/1 |
博弈:对于每个个体来讲都要是最优的。
P1288 取数游戏Ⅱ#
#
取数游戏。给出一个环,每条边都有为负边权,所有边中至少有一个 .
将一枚硬币放在环的一个节点上,两个玩家以这个放硬币的节点为起点开始游戏,轮流进行操作,操作方案如下:
- 选择当前硬币连出的两条边中的一条,要求这条边的权值为正;
- 将选取的这条边的权值,减少到任意一个,小于原本权值的非负整数;
- 将硬币移动到选取边的另一侧。
如果轮到一个玩家,硬币的两条边权都是 ,那么输掉游戏,比如:
#
显然每次操作,把边权直接变为 最优。
所以分别看起点的两端,如果它们的其中一个到第一个 的距离为奇数,那么先手胜,反之后手胜。
巴什博奕#
#
有 个物品,A
, B
两人轮流从物品中拿走一部分,每次至少拿 个,最多拿 个,拿走最后一个的人获胜。是否有必胜策略,若有,则求出策略。
#
可以想到,当物品的总量 时,A
先拿走 个物品,之后每次若B
取走 个,则 A
对应的取走 个,这样A
显然能够取得胜利。
若物品总量为 ,此时B用同样的策略即可,B必胜。
#
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
if(n % (m + 1) == 0) {
cout << "后手胜" << endl;
} else {
cout << "先手胜" << endl;
}
return 0;
}
cpp威佐夫博弈#
#
有两堆,若干个物品,两个人轮流从某一堆或从两堆中取同样多的物品,规定每次最少拿走一个,最多不限制,最后拿光的人获胜。
#
用A
表示先手玩家,B
表示后手玩家,当前是A
进行操作。为了判断 A
是否有必胜策略,可以这样分析:
- 用有序数对 表示两堆物品的数量状态;
- 是 的必败策略,因为此时 已输;
- 若局面能够移动到 无必胜策略的局面,那么这个局面是 的必胜态;
- 若局面无论怎么移动,都会变成 的必胜太,那么这个局面是 的必败态。
通过枚举,可以发现 的必败态有:
用有序数对序列表示上面的必败态,可以看出,满足: 且 , 是之前没有出现过的最小自然数。
#
前置命题: 是前 组必败态中没有出现过的最小正整数。#
证明命题: .#
考虑归纳证明。若前 个必败态分别为 ,下证:第 个必败态为 .
从这个必败态出发,一共可能走向三类状态:从第一堆拿走,从第二堆拿走,或者两堆同时拿走,只需证明这三类都是必胜态。
**情况一:**从第一堆拿走一些。
因为任意一个比 小的数都在之前的必胜态中出现过,第一堆变少之后,只需要把第二堆拿到第一堆对应的必败态。所以对于出现的新的状态,都可以直接指向必败态。
**情况二:**从第二堆拿走一些。
-
拿完之后第二堆仍然大于第一堆。此时可以表示成 ,那么两堆同时拿掉一些之后,变成 .
- 拿完之后第二堆小于第一堆,设其拿成了 ,则 一定属于之前的一个必败态,将第一堆拿到第二堆对应的必败态即可。
情况三:两堆同时拿走一些。
设拿完之后的状态是 。因为 ,所以第一堆一定属于之前的一个必败态,将第二堆拿到第一堆对应的必败态即可。
所以现在已经得到了数列关系式 .
#
在这个问题中,我们称必败态为奇异局势,记为 那么有如下性质:
1. 任何自然数都包含在一个且仅一个奇异局势中#
因为 是之前没有出现过最小自然数,所以所有自然数都会被包含到奇异局势当中。
由于 ,所以 。整理后,有 ,所以不存在自然数被两个奇异局势包含。
2. 任意操作都可以将奇异局势变为非奇异局势#
也就是说,必败态的后继一定都是必胜态。
对于奇异局势 ,如果只改变其中一个数,那么由性质,不可能是另一个奇异局势;
如果同时改变两个数,那么这两个数的差不变,由于 ,对于一个差值,只可能对应一个奇异局势,所以变化后也不可能是另一个奇异局势。
3. 可以将非奇异局势变为奇异局势#
根据奇异局势满足 ,可以分类讨论进行证明。
具体地,对于任意的局势 ,
若 ,那么同时取走 个物品即可变为 ;
若 是一个奇异局势中的 ,且 ,那么从 堆中取出 个物品,变为 .
若 是一个奇异局势中的 ,且 ,那么从两堆中同时拿走 个物品。
若 不属于任何一个奇异局势中,那么 一定是一个奇异局势中的 ,此时有两种情况:
如果 ,从第一堆中拿走多余的 .
如果 ,如果 ,就从第二堆中拿走 ;如果 ,就从第二堆里面拿走 .
#
快速判断局势 是否是奇异局势的方法。
Beatty定理#
设 是正无理数,且 ,记 , 为任意正整数。
那么 且 .
也就是说:取两个正无理数 ,构造两个数列 和 ,那么两个数列单调递增,并且每个正整数在两个数列当中出现且仅出现一次。
推导#
发现奇异局势和 Beatty 定理的条件很像,考虑计算出 和 .
两个数列 和 就是奇异局势中的 .
.
所以
所以 .
解得 .
所以 ,.
所以根据 Beatty 定理,奇异局势的通项公式为:,.
而 是黄金比,所以威佐夫博弈的奇异局势与黄金比相关。
判断方法#
根据奇异局势的性质,对于需要判断的局势 ,设 ,可以计算出 和 ,直接和 进行比较即可。

#
#include <bits/stdc++.h>
using namespace std;
bool eq(double a, double b) {
if(abs(a - b) < 1e-4) return true;
else return false;
}
int main() {
double a, b; cin >> a >> b;
if(a > b) swap(a, b);
double t = (1.0 + sqrt(5.0)) / 2.0;
double k = b - a;
if(eq(floor(k * t), a) && eq(floor(k * t * t), b)) {
cout << 0 << endl;
} else {
cout << 1 << endl;
}
return 0;
}
c必胜必败分析#
概念#
更明确的定义概念:
必胜点(N)#
先手必胜点。
必败点(P)#
先手必败点。
性质#
所有终结点都是必败点;
从必胜点出发,存在一种方法能够进入必败点;
从必败点出发,无论如何都只能进入必败点。
算法#
设计一种算法来分析,一个局面是先手必胜还是先手必败。
- 将终结点标为必败点;
- 将所有一步操作进入必败点的位置标为必胜点;
- 如果从某一个点开始,一步操作后只能进入必胜点,那么标记为必败点;
- 重复步骤2, 3.
记忆化搜索的过程,可以画出博弈树。
例子#
描述#
一个 行 列的矩阵,有一个小球放在左上角。两个人依次移动小球,每次只能:
- 向右移动一格;
- 向下移动一格;
- 向右下移动一格。
最后无路可走的人输。两人按最优策略,问谁必胜。
解析#
直接用 行 列的矩阵表示必胜必败情况。用*
表示情况不确定,P
表示必败,N
表示必胜。
首先标记最终的点是必败点:
***
***
***
**P
plaintext之后推第四行。第四行的所有点,只能往右走,所以:
***
***
***
PNP
plaintext再看第三行。 可以往下走到必败,所以是必胜点。 可以往右下走到必败,所以是必胜点, 同理,所以:
***
***
NNN
PNP
plaintext同样的,可以算出最后两行:
NNN
PNP
NNN
PNP
plaintext所以,先手必胜。
代码实现#
两个人从一堆 个物品中轮流取物品,每次取 个物品,最后没法取的人输。分析必胜必败。
bool win[1001], vis[1001];//win[i]表示剩余i个物品时的必胜必败情况
void init() {
win[0]=false;
vis[0]=true;
}
void dfs(int x){
if(vis[x]){
return;
}
if(x>=1){
dfs(x-1);
}
if(x>=3){
dfs(x-3);
}
if(x>=4){
dfs(x-4);
}
win[x]=false;
if(x>=1&&!win[x-1]){
win[x]=true;
}
if(x>=3&&!win[x-3]){
win[x]=true;
}
if(x>=4&&!win[x-4]){
win[x]=true;
}
vis[x]=true;
return;
}
cppSG函数#
组合博弈游戏#
组合博弈游戏是这样一种博弈游戏:
- 有两个游戏者
- 有一个有限的游戏状态集
- 游戏规则规定了在每一个状态可以到达的后继状态集。如果在任意状态,双方的走步集合是相同的,那么这个游戏是公平的,否则是不公平的。比如象棋是不公平的,因为每个玩家只能移动自己的棋子。
- 两个人轮流操作
- 一般情况下,当一方无法操作时,无法操作者输,游戏结束。
我是真找不到别的定义了……
对于任意一个公平的组合博弈游戏(ICG),我们将每个可能的局面当成图论当中的一个点。
在这个图中的连边规则是:把局面向这个局面经过操作后可能得到的所有局面连边,构成一个有向图。如果这个游戏对于任意的局面 ,都不能通过一些操作,回到这个局面 ,那么构成的图是一个有向无环图(DAG)。
SG函数#
定义一个 函数,表示对于一个集合,不属于该集合的最小非负整数。比如 .
还是在这个博弈游戏中:两个人从一堆 个物品中轮流取物品,每次取 个物品,最后没法取的人输。
我们对每个状态进行分级,终结态的级别是 . 如果一个状态能够变为 ~ 级的状态,那么这个状态是 级状态。
状态 能够变成 级状态,称它为 级状态;
状态 只能变成 级,不能变成 级,所以它的级别为 ;
状态 可以变为 级,级别为 ;
状态 可以变为 级,级别为 ;
状态 可以变为 级,级别为 .
在博弈游戏当中,SG函数定义为: 是 的后继,也就是上面所说的状态等级。
对于一个ICG来说,状态 是必败态,当且仅当 .
例子#
两个人从一堆 个物品中轮流取物品,每次取 个物品,最后没法取的人输。
,
,
.
P1247 取火柴游戏#
#
给定数列 ,表示有 堆物品,第 堆物品的数量为 。
两人轮流从中取物品,规则:每次可以从一堆中拿走任意正整数个物品。先拿走最后一根的人胜利。
对于给定的数列,判断先手是否必胜,若必胜,输出第一次应该在哪堆取多少物品。
#
必胜必败分析#
先手必胜,当且仅当 。
对于其他 nim游戏
,假设状态表示为 ,那么先手必胜,当且仅当 ,证明BFS。
本题中 ,所以有上面的结论。
证明#
设 .
也就是证明:
- 当 时,存在一种取法,使得 .
- 当 时,无论怎么取物品, 都不等于 .
命题一#
因为 ,根据异或的定义,存在一堆物品 ,满足 ,那么就从第 堆取走 ,剩下 个物品。
此时,.
所以命题一成立。
命题二#
反证法。若否,则 时,存在一种取物品的方法使得 .
设取走第 堆的若干物品,第 堆剩余 个物品。
所以 .
把 的定义式代入,得到 ,产生矛盾。
所以命题二成立。
找方案的方法#
利用命题一。找到一堆 ,满足 ,将这一堆拿走 个物品,使得新的异或和为 ,满足题意。
#
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000008;
int T, n;
int a[MAXN];
int main() {
scanf("%d", &n);
int res = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
res ^= a[i];
}
if(res == 0) {
cout << "lose" << endl;
} else {
for(int i = 1; i <= n; i++) {
if((res ^ a[i]) < a[i]) {
cout << a[i] - (res ^ a[i]) << " " << i << endl;
a[i] = res ^ a[i];
break;
}
}
for(int i = 1; i <= n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
return 0;
}
cP2148 [SDOI2009]E&D#
#
有 堆石子,编号分别为 。将第 和 堆 ,归为同一组。第 堆石子的个数是正整数 .
定义分割操作:
- 任取一堆石子,不妨为第 堆,将其全移走;
- 从和第 堆同一组的石子中,取出若干正整数个石子,放到第 堆的位置上。操作后,两堆石子数都要大于 。
给定状态,问先手是否有必胜策略。
.
#
定义#
为 的二进制末尾首个 的出现位置(标号从 开始)。比如 .
为一组分别有 个石子的 值。
表示满足 的 构成的自然数集合。.
引理#
-
终止局面的 值为 ,即 .
-
所有后继的 值 .
结论#
- 等同于 二进制下 的位置集合。比如 .
- . 比如 .
证明#
考虑归纳证明。逐步放大 ,证明对于任意的 ,结论 成立,且对于任意的 ,结论 成立。
根据引理 ,结论 在 时正确。
假设已经证明结论 在 正确,考虑 包含元素 的条件。
由 的定义,需要 ,由结论 ,需要 .
因此, 的二进制第 位是 , 位都是 . 所以 的 位都是 ,第 位是 .
又因为 ,所以当包含元素 时,无论如何,二进制下 的第 位总为 .
所以,当且仅当 的二进制第 位为 时, 包含元素 ,结论 得证。
因为结论 成立,所以 .
又因为 在已经证明的 的范围内,根据 和 的定义,显然 .
故结论 得证。
解法#
根据上述结论,,可以快速地求出每组的 值。将所有组的 值异或起来,即可判断是否必胜。
#
#include<bits/stdc++.h>
using namespace std;
int T, n;
int lowzero(int x){
for (int i = 0;; i++, x >>= 1) {
if (!(x & 1)) return i;
}
}
int main() {
scanf("%d", &T);
while(T--) {
int ans = 0;
scanf("%d", &n); n >>= 1;
while(n--) {
int x, y;
scanf("%d%d", &x, &y);
ans ^= lowzero((x - 1) | (y - 1));
}
if(ans) printf("YES\n");
else printf("NO\n");
}
return 0;
}
c参考资料#
威佐夫博弈:https://blog.csdn.net/wu_tongtong/article/details/79295069 ↗
关于异或:https://blog.csdn.net/hndu__lz/article/details/61925818 ↗