博弈论

组合游戏分类(下文有非回合制零和博弈)

公平组合游戏

  • 1.游戏有两个人参与,知道游戏的所有信息。
  • 2.在游戏的任意时刻,游戏者可以做出的决策只与当前游戏的状态有关,与游戏者无关。
  • 3.游戏中的同一个状态不可能多次抵达,游戏以玩家无法行动为结束,且游戏一定会在有限步后以非平局结束。

非公平组合游戏

非公平组合游戏(Partizan Game)与公平组合游戏的区别在于在非公平组合游戏中,游戏者在某一确定状态可以做出的决策集合与游戏者有关。大部分的棋类游戏都 不是 公平组合游戏,如国际象棋、中国象棋、围棋、五子棋等(因为双方都不能使用对方的棋子)。

nim游戏

首先介绍一个经典的公平组合游戏——nim游戏。

nim游戏:n 堆物品,每堆有 ai 个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。

取走最后一个物品的人获胜。经典的问题是对于nim游戏的一个状态是否存在一种必胜或必败的策略。那么如何解决这个问题呢?

nim游戏的各个状态之间的关系其实组成了一张有向无环图,这事就可以用到一种特殊的函数——SG函数。

SG函数

定义 mex 函数的值为不属于集合 S 中的最小非负整数,即:

mex(S)=min{x}(xS,xN)
例如 mex({0,2,4})=1mex({1,2})=0

对于状态 x 和它的所有 k 个后继状态 y1,y2,,yk,定义 SG 函数:

SG(x)=mex{SG(y1), SG(y2),,SG(yk)}
而对于由 n 个有向图游戏组成的组合游戏,设它们的起点分别为 s1, s2, ,sn,则有定理:当且仅当 SG(s1)SG(s2)SG(sn)0 时,这个游戏是先手必胜的。同时,这是这一个组合游戏的游戏状态 xSG 值。

这一定理被称作 SpragueGrundy 定理(SpragueGrundyTheorem), 简称 SG 定理。

例题:

eg1. P2197 【模板】nim 游戏

题意:

就是上文的nim游戏板子,有必胜策略输出“Yes”,否则输出“No”

解法:

我们只需要找到nim游戏的“节点”根据SG函数就能够判断。

我们可以将一个有 x 个物品的堆视为节点 x,则当且仅当 y<x 时,节点 x 可以到达 y

那么,由 n 个堆组成的 nim 游戏,就可以视为 n 个有向图游戏了。

根据上面的推论,可以得出 SG(x)=x。再根据 SG 定理,就可以得出 nim 和的结论了。

#include<bits/stdc++.h>
using namespace std;
int t;
int n;
int ans;
int a[10001];

int main(){
	cin>>t;
	while(t--){
		cin>>n;
		ans=0;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			ans^=a[i];
		}
		if(ans==0) cout<<"No"<<endl;
		else cout<<"Yes"<<endl;
	}
	return 0;
} 

eg2.[AGC002E] Candy Piles

题意:

桌上有 n 堆糖果,第 i 堆糖果有 ai 个糖。两人在玩游戏,轮流进行,每次进行下列两个操作中的一个:

  1. 将当前最大的那堆糖果全部吃完

  2. 将每堆糖果吃掉一个

吃完的人输,假设两人足够聪明,问谁有必胜策略?

输出 First(表示先手必胜)或 Second(表示后手必胜)

解法:

把样例拿过来:

9
7 6 7 7 2 2 4 4 4

先从大到小排序:

9
7 7 7 6 4 4 4 2 2

可以把它输入位置当作x轴,大小当作y轴,得到下图:

第一种操作其实就是把最左边的一列删除,而第二种操作则是把最下面的一行删除。

从原点开始,每人每次可以选择向右或向上移动一格,向右代表消去最左边一行,向上代表消去最下面一行。很容易发现,当走到网格图的边界(下图中的实线部分)时,所有糖刚好被吃完。

不难理解边界上就是必败态。而如果一个点上方和右方都是必败态,这个点就是必胜态,否则是必败态。但是a[i]的最大值是1e9所以直接扫图时间复杂度是不可接受的。(圈是必败态,叉是必胜态)

通过仔细观察,我们发现一条对角线上的状态都是一样的,所以我们只需要关注原点所在对角线上的状态即可(y=x)。我们找到以原点为左下角的最大正方形,设其右上方顶点为 (i,i) 。当它到最上面且不在边界上的点的距离和最右面且不在边界上的点的距离其中一个为奇数时,这个点为必败点,反之这个点为必胜点。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[100002];

bool cmp(int a,int b){
	return a>b; 
}
int fin;
int pls1,pls2;

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n+1;i++){
		if(a[i]<i || a[i+1]<=i){
			fin=i-1;
			break;
		}
	}
	int j=0;
	for(;a[j+fin+1+1]==fin+1;j++) 1;
	
	if(((a[fin+1]-fin-1&1) || (j&1))) cout<<"First";
	else cout<<"Second";
	return 0;
}

eg3.CF794E Choosing Carrot

题意:

下个月就是奶牛Z的生日啦,为了给奶牛Z购买生日礼物,奶牛A和奶牛B决定去挑选奶牛Z最喜欢的青草来作为送给奶牛Z的生日礼物。
现在,奶牛A和奶牛B买来了n堆青草,从左数起,第i堆青草的甜度为ai。奶牛A认为奶牛Z喜欢甜的青草,而奶牛B认为奶牛Z喜欢不甜的青草。因此,奶牛A希望选出来的青草是最甜的,奶牛B希望选出来的是最不甜的青草。
为了解决这个问题,奶牛A与奶牛B决定玩一个游戏,他们俩每次可以从两端的青草开始,选择其中一堆并把这一堆青草吃掉,最后剩下的那一堆青草就是送给奶牛Z的生日礼物,奶牛A先开始吃。
在玩游戏之前,奶牛B去上了一次厕所,奶牛A乘机进行了K次操作,每次操作也是按照要求从这些草堆当中,选择两端的草堆并吃掉其中一堆。在奶牛B回来之后,同样也是奶牛A先开始吃。
奶牛A想知道,对于每一个K0Kn1),最后送给奶牛Z的青草甜度分别是多少?

1n3105,1ai109

解法:

先考虑A奶牛不先选的情况,因为奶牛B是后手,所以在偶数情况下除了mid,mid+1堆青草它都是可以选得到的,所以答案是这两堆得较大值。在奇数情况下,先让奶牛A选一次,就变成了奶牛B先手的偶数情况。这时在中间两堆中,只能选到最小的(因为奶牛A可以选第一堆,也可以选最后一堆,所以中间两堆有两种情况,min(mid,mid1)min(mid,mid+1),这两者取较大值)

然后考虑进行 k 操作

A多吃,只有 mid 会被影响,发生变化,而每个 i[1,k2] 是由 i+2 转移过来的,三种可能:

要么左右各拿一个 mid 不变,要么左边或者右边拿俩 mid 变化,而我们考虑奇偶即可,左右拿俩可以合并到一个数组,但是如果要求左右拿俩的情况,则需要用到动态规划

h[i] 为偶数个数当 i 为中点时最大值, g[i] 为奇数个数当 i 为中点时最大值, ans[i] 为剩下 i 个元素能取到的最大值,转移方程为:

ans[2i]=max(ans[2i+2],h[i])

ans[2i+1]=max(ans[2i+3],g[i])

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[300001];
int lf[300001];
int rf[300001];
int ans[300001];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		ans[1]=max(ans[1],a[i]);
	}
	for(int i=1;i<=n;i++){
		//int mid=(i+n)/2; 
		lf[min(i,n-i)]=max(lf[min(i,n-i)],max(a[i],a[i+1]));
		rf[min(i-1,n-i)]=max(max(rf[min(i-1,n-i)],min(a[i-1],a[i])),min(a[i],a[i+1]));
	}
	for(int i=n/2;i>=1;i--){
		ans[i*2]=max(ans[2*i+2],lf[i]);
		ans[2*i+1]=max(ans[2*i+3],rf[i]);
	}
	for(int i=n;i>=1
	;i--) cout<<ans[i]<<" ";
	return 0;
}

nim游戏的变形

1.nim-k游戏

大致与nim游戏相同,只不过一次操作不是选1n堆而是1k堆。

结论:把每一堆转化为二进制数。每个二进制位上为 1 的个数 (k+1)=0 则是必败局面。

证明:一篇blog

eg4.P2490 [SDOI2011] 黑白棋

题意:

小 A 和小 B 又想到了一个新的游戏。

这个游戏是在一个 1×n 的棋盘上进行的,棋盘上有 k 个棋子,一半是黑色,一半是白色。

最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。

小 A 可以移动白色棋子,小 B 可以移动黑色的棋子,其中白色不能往左,黑色不能往右。他们每次操作可以移动 1d 个棋子。

每当移动某一个棋子时,这个棋子不能跨越两边的棋子,当然也不可以出界。当谁不可以操作时,谁就失败了。

小 A 和小 B 轮流操作,现在小 A 先移动,有多少种初始棋子的布局会使他胜利呢?

输入三个数 n,k,d

输出小 A 胜利的方案总数。答案对 109+7 取模。

  • 对于 30% 的数据,有 k=2
  • 对于 100% 的数据,有 1dkn104k 为偶数,k100

解法:

我们把相邻的黑白棋子之间的距离当作一堆石子的个数,转化为典型的nimk游戏,接下来设dp[i][j]为确定异或和第1i位均为0,有j个石子的方案数。dp不是博弈论内容,在此不再赘述。

2.反nim

顾名思义游戏是相同的,只不过取走最后一个的是败者。

结论:先手必胜:1:存在偶数堆石子且石子数为1

2:至少存在一堆石子数大于1且石子数异或不为0

反之必败

证明:另一篇blog

eg5.LightOJ 1253 Misere NIM(反NIM博弈)

实在是找不到网址。

题意:

给定有 T组 数据,每组数据有一个数m,然后接下来又m个数,代表的是每一堆有a[i]块石子,然后两个人AliceBob 玩游戏,Alice先手,每次可以取每一堆的任意石子但是不能不取,谁最后取完谁输。

解法:

就是反nim游戏。

#include<bits/stdc++.h>
using namespace std;
int T,n,x,ans,cnt;
int main(){
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%d",&n);
        cnt=ans=0;
        for(int i=1;i<=n;++i){
            scanf("%d",&x);
            if(x==1) cnt++;
            ans^=x;
        }
        if(cnt==n){
            if(ans==0) printf("Case %d: Alice\n",cas);
            else  printf("Case %d: Bob\n",cas);
        }
        else{
            if(ans) printf("Case %d: Alice\n",cas);
            else printf("Case %d: Bob\n",cas);
        }
    }


    return 0;
}

3.阶梯nim游戏

现在,有一个n个台阶的楼梯,每一级台阶上有若干个石子。两位玩家轮流操作,每次操作可以将任意一级台阶上拿若干个石子放到下一级台阶中,已经拿到地面上的石子不能再拿,无法操作的人失败。

结论:奇数集异或和=0   ⟺ 必败;


下文是一道反方向的阶梯nim游戏

有N堆石子,除了第一堆外,每堆石子个数都不少于前一堆的石子个数。 两人轮流操作每次操作可以从一堆石子中移走任意多石子,但是要保证操作后仍然满足初始时的条件谁没有石子可移时输掉游戏。问先手是否必胜。

eg6.BZOJ 1115: [POI2009]石子游戏Kam (阶梯nim)

解法:

这一题的关键就是给出的序列是单调不降的。所以我们可以考虑对原序列差分。然后我们考虑拿走石子的意义其实就是减小该堆与前一堆的差值,加大该堆和后面的差值,倒着把奇数层异或起来。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10,INF=1e9+10;
int QWQ; 
inline char nc(){
    static char buf[MAXN],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin),p1==p2)?EOF:*p1++;
}
int a[MAXN];
int main(){
    cin>>QWQ;
    while(QWQ--){
        int N,ans=0;
        cin>>N;
        for(int i=1;i<=N;i++) cin>>a[i];
        for(int i=N;i>=2;i--) a[i]=a[i]-a[i-1];
        for(int i=N;i>=1;i--) 
            if( (N-i+1)&1 ) ans=ans^a[i];
        puts(ans?"TAK":"NIE");
    }
    return 0;
}

非回合制零和博弈

其实就是在组合游戏的基础上增加一个条件:对于一步操作,它会对一方带来收益,而另一方支付相应的损失。

不难得出:

  • 1.在任意时刻,博弈双方的收益和恒为0
  • 2.如果双方同时,对于对方的所有决策,一般不存在同一种最优决策。这时问题就变成了混合决策问题。

混合决策问题:

我们假设一位玩家有n种决策方式,另一位有m种决策方式。那么对于一位玩家A来说,他的收益情况是一个n * m 的矩阵。对于这个矩阵里A的收益e[i][j],B的对应收益为-e[i][j]。

怎么决策:

显然双方的最终目标是最大化自己的收益(相当于最大化对方的支出)。直观上来说。若A选择了决策a,相应的B会选择决策b使得e[a][b]取得最小值。直观上来说,我们要选取一个概率序列{l1,l2,l3....,ln}(A选择1-n决策的概率)使得i=1n e[ai][bi]li取得最大值。当一方采用最优策略时,另一方无论怎么调整都不会是收益更大。这个概念叫做纳什平衡。

纳什平衡:

本文提到的问题为混合策略纳什均衡,纳什平衡其实还分为纯策略纳什均衡。
举一个最典型的问题(钱币问题)

硬币正反问题:

假如你正在图书馆枯坐,一位陌生美女主动过来和你搭讪,并要求和你一起玩个数学游戏。美女提议:“让我们各自亮出硬币的一面,或正或反。如果我们都是正面,那么我给你3元,如果我们都是反面,我给你1元,剩下的情况你给我2元就可以了。”那么该不该和这位姑娘玩这个游戏呢?这基本是废话,当然该。问题是,这个游戏公平吗?
假设我们出正面的概率是x,反面的概率是1-x,美女出正面的概率是y,反面的概率是1-y。为了使利益最大化,应该在对手出正面或反面的时候我们的收益都相等(不然在这个游戏中,对方可以改变正反面出现的概率让我们的期望收入减少),由此列出方程就是:
3x+(2)(1x)=(2)x+1(1x)

解方程得x=3/8

同样,美女的收益,列方程:

3y+2(1y)=2y+(1)(1y)

解得y也等于3/8,而美女每次的期望收益则是 2(1y)3y=1/8元。这告诉我们,在双方都采取最优策略的情况下,平均每次美女赢1/8元。而我们则亏1/8元。

具体求解可以看:基础博弈论学习笔记

posted @   星河倒注  阅读(143)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示