博弈论

前言
  • 本文中的博弈论的详细介绍与讲解主要针对于信息学竞赛,可能存在部分观点与广义相对论有所矛盾。
  • 部分资料来源于网络,已标明来源。
  • 转载或引用请标明出处。

一、要素

  1. 局中人:在一场竞赛或博弈中,每一个有决策权的参与者成为一个局中人。只有两个局中人的博弈现象称为“两人博弈”,而多于两个局中人的博弈称为“多人博弈”。
  2. 策略:一局博弈中,每个局中人都有选择实际可行的完整的行动方案,即方案不是某阶段的行动方案,而是指导整个行动的一个方案,一个局中人的一个可行的自始至终全局筹划的一个行动方案,称为这个局中人的一个策略。如果在一局博弈中局中人都总共有有限个策略,则称为“有限博弈”,否则称为“无限博弈”。
  3. 得失:一局博弈结局时的结果称为得失。每个局中人在一局博弈结束时的得失,不仅与该局中人自身所选择的策略有关,而且与全局中人所取定的一组策略有关。
  4. 对于博弈参与者来说,存在着一博弈结果 。

摘自百度百科。

二、经典模型

从目录中跳转。

  • 巴什博弈(\(\mathcal{Bash\;game}\)

  • 威佐夫博弈(\(\mathcal{Wythoff's\;game}\)

  • 尼姆游戏(\(\mathcal{Nim\;Game}\)

  • SG 函数

A. 巴什博弈

双人博弈。

问题

有一堆总数为 \(n\) 的物品,\(2\) 名玩家轮流从中拿取物品。每次至少拿 \(1\) 件,至多拿 \(m\) 件,不能不拿,最终将物品拿完者获胜。

结论

\((m+1)\mid n\),则后手必胜,否则先手必胜。

注:这里的必胜以及下文的均表示有必胜策略。

简证

\(n=k(m+1)+d\),其中 \(k\in \mathcal{N}\)\(d\in \left[ \,0,m\,\right]\)

\(d= 0\) 时,设先手方拿走 \(x\) 个物品,则若后手每次拿取 \(m+1-x\) 个,那么每次后手行动后,剩余的物品数量仍然是 \(m+1\) 的倍数,直到最后一回合剩余 \(m+1\) 个,无论先手如何操作,最后总能剩下 \(\left[\,1,m\,\right]\) 个物品,后手获胜。

\(d\neq 0\) 时,由上定义可知,若先手方第一轮拿取 \(d\) 个,使得余下的数量为 \(m+1\) 的倍数,那么此时的先手就会成为 \(d=0\) 下的后手,按上述策略行动,则必获胜。

Code:

点击查看代码
namespace Bash
{
	short main()
	{
		int n,m;
		scanf("%d%d",&n,&m);
		if(n%(m+1)==0) printf("Player2 win.\n");
		else printf("Player1 win.\n");
		return 0;
	}
}

B. 威佐夫博弈

双人博弈。

问题

有两堆石子,两名玩家每次可以从任意一堆石子中取任意多的石子或者从两堆石子中取同样多的石子,最后取完者胜。

结论

枚举观察可以发现,在遇到特定的局势时,先手必败,我们称其为奇异局势。

在 OI 中应用较少。

判断奇异局势

证明见 百度百科

设当前局势为 \(\left(x,y\right)\),其中 \(x\lt y\),函数 \(\left[ x \right]\) 表示不超过 \(x\) 的最大整数,令 \(p=\frac{1+\sqrt{5}}{2}\),则形如 \(\left( \left[ k\times p\right],\left[k\times p^2\right]\right)\) 的局势一定为奇异局势。

转换成判断形式为 \(\left(y-x\right)\times p = x\),满足即为奇异局势。

Code:

P2252

这道题卡精度,需要手写 sqrt 并且调用 std 库才可 AC。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
namespace Wythoff
{
	long double Wsqrt(long double n)
	{
		long double s=sqrt(n);
		for(int i=1;i<=n;i++) s=n/s;
		return s;
	}
	short main()
	{
		long long x,y,z;
		scanf("%lld%lld",&x,&y);
		z=abs(x-y)*((Wsqrt(5)+1)/2.0L);
		if(z==min(x,y)) printf("0\n");
		else printf("1\n");
		return 0;
	}
}
int main(){return Wythoff::main();}

C. 尼姆游戏

双人博弈。

问题

有若干堆石子,每堆石子的数量都是有限的,合法的移动是选择一堆石子并拿走若干大于零颗,最后取完者胜。

结论

设一种局势的状态为 \(\left(a_1,a_2,\ldots,a_n\right)\),则若 \(a_1\oplus a_2\oplus \ldots \oplus a_n=0\),则后手必胜,否则先手必胜。

简证

定义 P-position(Previous) 为后手必胜局势,简称 P 状态;N-position(Next) 为先手必胜局势,简称 N 状态。

我们将每一种状态视为一个节点,并将其向其后继状态连边,这样就得到了一个博弈状态图。

那么显然的是,入度为 \(0\) 的点为初始状态,出度为 \(0\) 的点为最终游戏结束时的状态,并且为 P 局势(在结束时的状态先手的人显然无法有任何操作)。

那么通过推理,我们还可以得到两条定理:

  1. 一个状态为 N 状态当且仅当存在至少一个 P 状态为它的后继状态。

  2. 一个状态是 P 状态当且仅当它的所有后继状态均为 N 状态。

解释一下。

对于定理 1,若该状态存在至少一个 P 状态,那么玩家可通过操作到达该状态,使对手进入 P 状态,进而自己必胜。

对于定理 2,若该状态的所有后继状态都为 N 状态,即无论如何操作都会使对手进入 N 状态,进而自己必败。

若该博弈状态图是一个有向无环图,那么根据这些已知定理,我们可以用 \(\mathcal{O}(N+M)\) 的时间得出每个状态是 P 状态还是 N 状态。

但是复杂度太高,于是我们考虑结论中 \(\mathcal{O(1)}\) 的判断方法。我们先设 \(k=a_1\oplus a_2 \oplus\ldots\oplus a_n\)

若想得到该结论,只需证明一下两个定理:

  1. 对于 \(k \neq 0\) 的状态,存在至少一种后继状态使得 \(k =0\)

  2. 对于 \(k=0\) 的状态,所有的后继状态的 \(k\) 值均不为 \(0\)

image

来自 OI-wiki。

Code:

P2197

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int T;cin>>T;
    while(T--)
    {
        int n,ans=0;cin>>n;
        for(int i=1;i<=n;i++)
        {
            int a;cin>>a;ans^=a;
        }
        if(ans==0) printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}

应用——SG 函数

引入

  • \(\mathcal{Sprague–Grundy}\) 定理

定义 mex 函数为不属于集合 S 中的最小自然数,即 \(mex(S)=min\{ x \} \left(x\notin S,x\in N\right)\)

对于一个状态 x 和它的所有后继状态 \(y_1,y_2,\ldots , y_n\),定义 SG 函数为:

\[SG(x)=mex\{SG(y_1),SG(y_2),\ldots,SG(y_n)\} \]

对于由 \(n\) 个有向图游戏组成的组合游戏,设它们的起点分别为 \(s_1,s_2,\ldots,s_n\),那么当且仅当 \(SG(s_1)\oplus SG(s_2)\oplus\ldots\oplus SG(s_n)\neq 0\) 时,先手必胜。这就是 SG 定理。

  • 一个游戏的 SG 函数
    讲一个游戏 \(X\) 拆分成若干个子游戏 \(s_1,s_2,\ldots,s_n\),那么该游戏的 SG 函数值为:

\[SG(X)=SG(s_1)\oplus SG(s_2)\oplus \ldots\oplus SG(s_n) \]

简证

image

摘自 OI-wiki。

三、例题

A. Game On Tree

AGC017D

很基础的 SG 函数应用题,只需要求出整棵树的 SG 值即可。

但真的只是用求所有子树的 SG 值异或和吗?

仔细观察,这道题其实可以转化为树上的关于边的尼姆游戏,每一棵子树到根还有一条未被算入的边,因此正确的结果应该为:

\[SG(X)=(SG(a_1)+1)\oplus (SG(a_2)+1)\oplus\ldots\oplus (SG(a_n)+1) \]

Code:

点击查看代码
#include<bits/stdc++.h>

const int Ratio=0;
const int N=2e5+5;
int n,m;
int hh[N],ne[N<<1],to[N<<1],cnt;

namespace Wisadel
{
    void Wadd(int u,int v)
    {
        to[++cnt]=v;
        ne[cnt]=hh[u];
        hh[u]=cnt;
    }
    int Wdfs(int u,int fa)
    {
        int sum=0;
        for(int i=hh[u];i!=-1;i=ne[i])
            if(to[i]!=fa) sum^=Wdfs(to[i],u)+1;
        return sum;
    }
    short main()
    {
        memset(hh,-1,sizeof hh);
        scanf("%d",&n);cnt=0;

        for(int i=1,a,b;i<n;i++)
            scanf("%d%d",&a,&b),
            Wadd(a,b),Wadd(b,a);

        if(Wdfs(1,0)) printf("Alice\n");
        else printf("Bob\n");
        return Ratio;
    }
}
int main(){return Wisadel::main();}

B. Alice 和 Bob 又在玩游戏

P6665

可以显然看出这是一个用 SG 函数求解的问题,显然我们只需对每个连通块计算一遍其 SG 值异或起来检验是否非零即可。

这道题删的边是与根节点联通的边,也就是删边操作后会形成森林,这些森林也就是每个状态的后继。我们发现,不同的树之间的操作互不影响,将它们看做不同的游戏,那么该后继状态的 SG 值其实就是每一个游戏的 SG 值的异或和。

根据 SG 定理,我们的任务转化成了快速求出所有树的 SG 值的mex 值。

考虑用 0-1trie 维护,可以达到 \(\mathcal{O(n\,logn)}\) 的复杂度。

四、末

咕。


完结撒花~

posted @ 2024-07-24 17:03  DrRatio  阅读(388)  评论(2编辑  收藏  举报