不平衡博弈问题还没学,别急
博弈论入门
- 必败情况为P,必胜情况为N,我们要得出N一定有方法能转换到P,P任意操作都会到N
1.巴什博弈
-
两个顶尖聪明的人在玩游戏,有一堆n个石子,每次每个人能取 [ 1 , m ] [1,m] [1,m]个石子,不能拿的人输,请问先手与后手谁必败?
-
1~m个石子,先手必胜
-
反推m+1个石子只能到1~m,所以必败
-
反推m+2~2*m+1一定能到m+1,所以必胜
-
综上所述,当石子数为m+1的倍数时必败,否则必胜
2.尼姆博弈
-
两个顶尖聪明的人在玩游戏,有n堆石子,第 i i i堆有 a i a_i ai个,每人每次能从一堆石子中取任意多个石子但不能不取,不能拿的人输,请问先手与后手谁必胜?
-
最后所有数都为0,所以异或和为0
-
反推一步,将一个数改变,那么异或和一定不为0
-
也就是说先手的人保持自己的异或和为0就能获得最后的胜利,如果本来就是0就输
-
那么一定有保持自己异或和为0的取法吗?
-
假设异或和为x,那么一定有一个数在这个位上为1,让它和x异或,它一定会变小,因为x最高位变成了0,无论后面是多少都会变小。
-
结论:异或和为0后手赢,反之先手赢
nimk游戏
-
如果将游戏改成每人每次可以从 [ 1 , d ] [1,d] [1,d]堆中各取任意多个石子呢?
-
每个二进制上为1的个数%(1 + d) = 0为必败情况
-
以下为反推过程
-
全0为P
-
对于P状态,每次操作 [ 1 , d ] [1,d] [1,d]个数,对于操作的最高位来说也就是减少 [ 1 , d ] [1,d] [1,d]次,无法变回%(1 + d) = 0
-
因此P任意操作都会到N得证
-
对于N状态,从高位处理到低位,如果高位有从1到0的,那么下面的位无论取0还是1都无所谓
-
对于最大位,取多余的1
-
对于下面的位,假设有n个高位从1到0,也就是说有n个可以任意换0或换1,有a个1和b个0,那么它的贡献就可以为 [ − a , b ] [-a,b] [−a,b],如果n到了d,这个范围就包括了 [ 1 , d ] [1,d] [1,d]
-
如果n没到d,那就可以取外面的1,照样可以让这个范围覆盖 [ 1 , d ] [1,d] [1,d],并且将这个范围扩大
-
因此N有方法到P得证
-
综上所述每个二进制上为1的个数%(1 + d) = 0为必败情况,否则必胜
3.威佐夫博弈
- 两个顶尖聪明的人在玩游戏,有2堆石子,每人每次可以拿走任意一堆中任意数量的石子或在两堆石子中拿走相同数量的石子,不能拿的人输,请问先手与后手谁必胜?
- 结论:
(
⌊
1
+
5
2
∣
y
−
x
∣
⌋
,
⌊
3
+
5
2
∣
y
−
x
∣
⌋
)
(⌊\frac{1+\sqrt5}2|y-x|⌋,⌊\frac{3+\sqrt5}2|y-x|⌋)
(⌊21+5∣y−x∣⌋,⌊23+5∣y−x∣⌋)必败,否则必胜,利用Betty定理
不会,再说
扩展威佐夫博弈
- 要求取的数的绝对值之差 ∣ x − y ∣ < d |x-y|<d ∣x−y∣<d,或者只取一个
- 结论:
(
⌊
2
−
d
+
d
2
+
4
2
k
⌋
,
⌊
2
+
d
+
d
2
+
4
2
k
⌋
)
(⌊\frac{2-d+\sqrt{d^2+4}}2k⌋,⌊\frac{2+d+\sqrt{d^2+4}}2k⌋)
(⌊22−d+d2+4k⌋,⌊22+d+d2+4k⌋),
y
=
x
+
d
k
y=x+dk
y=x+dk必败
背结论呗
4.斐波那契博弈
-
两个顶尖聪明的人在玩游戏,有一堆石子,先手第一次可以拿任意多个但不能全拿走也不能不拿,之后每个人最少拿一个,最多拿前一个人两倍那么多个,谁取到最后一个谁就能获胜,请问先手后手谁必胜?
-
结论:为斐波那契数时必败,否则必败
-
数学归纳法证明:斐波那契数列时,先手必败
-
当n等于2时显然先手必败
-
假设 n = f [ i ] ( i < = k ) n=f[i](i <= k) n=f[i](i<=k)的先手必败,现证明 n = f [ k + 1 ] = f [ k ] + f [ k − 1 ] n=f[k+1]=f[k]+f[k-1] n=f[k+1]=f[k]+f[k−1]是否先手必败
-
因为 f [ k + 1 ] − f [ k − 1 ] = f [ k ] = f [ k − 1 ] + f [ k − 2 ] < f [ k − 1 ] ∗ 2 f[k+1]-f[k-1]=f[k]=f[k-1]+f[k-2]<f[k-1]*2 f[k+1]−f[k−1]=f[k]=f[k−1]+f[k−2]<f[k−1]∗2所以先手不能取大于 f [ k − 1 ] f[k-1] f[k−1]的数
-
那么根据 n = f [ k − 1 ] n=f[k-1] n=f[k−1]时先手必败,将会是后手取完 f [ k − 1 ] f[k-1] f[k−1]所以问题就转换为 n = f [ k ] n=f[k] n=f[k]时先手必败
-
再证明:不是斐波那契数列时,先手必胜
-
首先根据齐肯多夫定理:任何整数可以分解成若干个不连续的斐波那契数之和,可以将n分解成a+b+c+…
-
取最小的斐波那契数a,因为不连续,所以b>2*a,这就转换为面对斐波那契数b先手必败,直到把所有的数取完
5.阶梯博弈
-
两个绝顶聪明的人在玩游戏,有n堆石子,每次每人可以取走第i堆(i>1)任意数量的石子并将它们放到第i−1堆,或者直接取走第一堆的任意数量石子,不能操作的人输,请问先手能否必胜?
-
转换为奇数位的尼姆博弈
-
从偶数位下放到奇数位,下一个人可以再将这些多的下放到下一个偶数位直到丢掉,所以对于后手来说,丢偶数位没有用
-
如果只丢奇数位,就变成了n/2堆石头,尼姆博弈
SG函数解决公平组合问题
公平组合问题:
- 游戏有两个人参与,二者轮流做出决策,双方均知道游戏的完整信息;
- 任意一个游戏者在某一确定状态可以作出的决策集合只与当前的状态有关,而与游戏者无关(如象棋就为非公平组合游戏,因为你不能操作对手的棋子,那么你可以操作的集合就与对手不同);
- 游戏中的同一个状态不可能多次抵达,游戏以玩家无法行动为结束,且游戏一定会在有限步后以非平局结束。
SG值:
- 定义SG值为不属于可到集合的SG值的最小非负整数
- 定义必败状态SG为0
- 那么必败状态(SG值 = 0)必定不能到必败状态
- 必胜状态(SG值 != 0)必定能到必败状态(SG = 0)(因为可到集合SG值的最小值不为0)
SG值定理
- 若有多个这样的公平组合游戏,那么求它们的异或值,就是它的SG值,为0说明必败,否则必胜
- 如果一个公平组合游戏可以分解为多个,就求它们的异或值。
或者情况与分离情况
- 如果可以转移到A或B状态,那么直接取A,B状态的SG值即可
- 如果是可以转移到A与B共存状态,那么取A^B即可
例题
1.暴力型
每次切一刀,第一个切出1*1的方块的胜利
2 ≤ n , m ≤ 200 2\leq n,m \leq 200 2≤n,m≤200
有1时必胜,设SG值为1,其他递推即可
#include<bits/stdc++.h>
using namespace std;
int sg[201][201];
int main()
{
for(int i = 1;i <= 200;i++){
sg[i][1] = sg[1][i] = 1;
}
for(int i = 1;i <= 200;i++)
for(int j = 1;j <= 200;j++){
set<int> se;
int _sg = 0;
for(int _i = 2;_i <= i - 2;_i++){
se.insert(sg[_i][j]^sg[i-_i][j]);
}
for(int _j = 2;_j <= j - 2;_j++){
se.insert(sg[i][_j]^sg[i][j-_j]);
}
for(auto x:se){
if(_sg == x){
_sg++;
}else
break;
}
sg[i][j] = _sg;
}
int w,h;
while(cin >> w >> h){
if(sg[w][h])
cout << "WIN" << endl;
else
cout << "LOSE" << endl;
}
}
2.打表找规律型
题意:给n个集合 [ l i , r i ] [l_i,r_i] [li,ri]和p,每次选一个集合和一个x,要求 x ∗ p ≥ r i x*p \geq r_i x∗p≥ri
思路:打个表求SG函数先
#include<bits/stdc++.h>
using namespace std;
int main()
{
int sg[1001];
int p = 3,l = 1;
for(int r = 1;r <= 100;r++){
set<int> se;
for(int x = r;x*p >= r;x--){
if(x <= l)
se.insert(0);
else
se.insert(sg[x-1]);
}
int _sg = 0;
for(auto x:se){
if(_sg == x)
_sg++;
else
break;
}
cout << (sg[r] = _sg) << ' ';
}
}
得出规律:前 q l − l + 1 ql-l+1 ql−l+1个从 1 1 1到 q l − l + 1 ql-l+1 ql−l+1,然后接上一个0,然后是长度为 p p p的循环递增节,如果不是p的整数倍,那么正常递增,否则所有后面的p的整数倍的形成和原序列相同的序列(用递归求)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll get_sg(ll l,ll r,ll p){
ll num = r - l + 1;
if(num <= p*l - l + 1)
return num;
else if(num == p*l - l + 2)
return 0;
else{
num -= p*l - l + 2;
if(num % p == 0)
return get_sg(l,l+num/p-1,p);
else
return (num/p)*(p - 1) + (num % p) + p*l - l + 1;
}
}
int main()
{
ios::sync_with_stdio(false);//写了using namespace std;
int t;cin >> t;
while(t--){
ll nim = 0;
int n;
ll p;cin >> n >> p;
for(int i = 1;i <= n;i++){
ll l,r;cin >> l >> r;
nim ^= get_sg(l,r,p);
}
if(nim == 0)
cout << "Second" << endl;
else
cout << "First" << endl;
}
}
A Funny Game
做过看到这题我就想笑
题意:一个环,每次可以取1~2连续的节点,取了点后就断开了环或者线,不能取的败
思路:一个环比较难直接求sg函数,但一条线可以分解为两条线,因此sg函数比较好求,而环也可以转换为环长-1与环长-2的线的问题,那么只要求环长-1与环长-2即可
打个表先
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int sg[N];
int main(){
sg[0] = 0;cout << 0 << ' ';
for(int i = 1;i <= 1e3;i++){
bool vis[N] = {0};
for(int j = 1;j <= i;j++){
vis[sg[i - j]^sg[j - 1]] = 1;
if(j > 1)
vis[sg[i - j]^sg[j - 2]] = 1;
}
int _sg = 0;
sg[i] = 0;
while(vis[_sg]){
sg[i] = ++_sg;
}
cout << sg[i] << ' ';
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Zi8z2T8-1688802435936)(C:\Users\Administrator\Desktop\markdown\打表.PNG)]
笑了,除了0必败外其他均必胜,也就是说作为环除了 n ≤ 2 n\leq2 n≤2的情况先手能胜利,其他情况先手均败
#include<iostream>
using namespace std;
int main(){
int n;
cin >> n;
while(n != 0){
if(n <= 2)
cout << "Alice" << endl;
else
cout << "Bob" << endl;
cin >> n;
}
}
那么我们来正常想一下,先手将一个环变成了一条线,那么后手在中间取1或者2,一定有方法将它分成完全相同的两份,那么后手只要模仿先手的操作,那么后手一定是取完所有点的人,因此除了 n = 1 , 2 n=1,2 n=1,2的情况,其他情况后手均必胜
想笑
G - A Chess Game
题意:N个位置,相互连接形成一个有向拓扑图,在这些位置上放棋子,每个人可以移动棋子一步,谁先不能移动就输
思路:深度为0,SG值就为0,反推即可,然后爆时间了,用dfs正向推,记忆化搜索即可,然后棋子放哪就取它的SG值异或即可
#include<iostream>
#include<stdio.h>
#include<set>
#include<queue>
#include<vector>
using namespace std;
const int N = 1e3 + 10;
int sg[N];
vector<int> ed[N];
int ask(int x){
if(sg[x] != -1) return sg[x];
int sz = ed[x].size();
bool use[N] = {0};
for(int i = 0;i < sz;i++){
use[ask(ed[x][i])] = 1;
}
sg[x] = 0;
int _sg = 0;
while(1){
if(use[_sg])
sg[x] = ++_sg;
else
break;
}
return sg[x];
}
int main(){
int n;
while(~scanf("%d",&n)){
queue<int> qu;
for(int i = 0;i < n;i++){
int num;scanf("%d",&num);
ed[i].clear();
for(int j = 1;j <= num;j++){
int x;scanf("%d",&x);
ed[i].push_back(x);
}
}
int m;
memset(sg,-1,sizeof(sg));
while(scanf("%d",&m),m){
int nim = 0;
for(int i = 1;i <= m;i++){
int x;scanf("%d",&x);
nim^=ask(x);
}
if(nim)
puts("WIN");
else
puts("LOSE");
}
}
}
H - S-Nim
题意:每次只能取S集合内数字的nim游戏
思路:总数最大为10000,集合内数100,暴力求SG就 1 0 6 10^6 106,直接求即可
#include<iostream>
#include<stdio.h>
#include<set>
#include<queue>
#include<vector>
using namespace std;
const int N = 1e4 + 10,M = 1e2 + 10;
int sg[N],s[M];
int n,m;
void getnim(){
for(int i = 1;i <= 1e4;i++){
bool vis[M] = {0};
for(int j = 1;j <= m;j++)
if(i - s[j]>= 0)
vis[sg[i - s[j]]] = 1;
int _sg = 0;
sg[i] = 0;
while(1){
if(vis[_sg])
sg[i] = ++_sg;
else
break;
}
}
}
int main(){
ios::sync_with_stdio(false);//写了using namespace std;
cin >> m;
while(m != 0){
for(int i = 1;i <= m;i++)
cin >> s[i];
getnim();
cin >> n;
while(n--){
int t;cin >> t;
int nim = 0;
for(int i = 1;i <= t;i++){
int x;cin >> x;
nim ^= sg[x];
}
if(nim)
cout << 'W';
else
cout << 'L';
}
cout << endl;
cin >> m;
}
}