「学习笔记」博弈论
作者:互联网
一. NIM 游戏
\(n\) 堆物品,每堆有 \(a_i\) 个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。
取走最后一个物品的人获胜。
例如,如果现在有 \(n=3\) 堆物品,而每堆分别有 \(2,5,4\) 个,那么可以取走第 \(1\) 堆中的 \(2\) 个物品,局面就变成了 \(0,5,4\);或者也可以取走第 \(2\) 堆的 \(4\) 个物品,局面就变成了 \(2,1,4\)。
如果现在的局面为 \(0,0,5\),甲取走了第 \(3\) 堆的 \(5\) 个物品,也就是取走了最后一个物品,此时甲获胜。
如果将每个状态视为一个节点,再从每个状态向它的后继状态连边,我们就可以得到一个博弈状态图。
例如,如果节点 表示局面为 时的状态,则我们可以画出下面的博弈图(由于篇幅有限,故仅显示部分状态节点和部分边):
二. 博弈图和状态
如果将每个状态视为一个节点,再从每个状态向它的后继状态连边,我们就可以得到一个博弈状态图。
例如,如果节点 \((i,j,k)\) 表示局面为 \((i,j,k)\) 时的状态,则我们可以画出下面的博弈图(由于篇幅有限,故仅显示部分状态节点和部分边):
定义 必胜状态 为 先手必胜 的状态,必败状态 为 先手必败的状态。
通过推理,我们可以得出下面三条定理:
- 没有后继状态的状态是必败状态。
- 一个状态是必胜状态当且仅当存在至少一个必败状态为它的后继状态。
- 一个状态是必败状态当且仅当它的所有后继状态均为必胜状态。
对于定理 \(1\),如果游戏进行不下去了,那么这个玩家就输掉了游戏。
对于定理 \(2\),如果该状态至少有一个后继状态为必败状态,那么玩家可以通过操作到该必败状态;此时对手的状态为必败状态——对手必定是失败的,而相反地,自己就获得了胜利。
对于定理 \(3\),如果不存在一个后继状态为必败状态,那么无论如何,玩家只能操作到必胜状态;此时对手的状态为必胜状态——对手必定是胜利的,自己就输掉了游戏。
如果博弈图是一个有向无环图,则通过这三个定理,我们可以在绘出博弈图的情况下用 \(O(n+m)\) 的时间(其中 \(n\) 为状态种数,\(m\) 为边数)得出每个状态是必胜状态还是必败状态。
三. NIM 和
让我们再次回顾 NIM 游戏。
通过绘画博弈图,我们可以在 \(O(\prod_{i=1}^n a_i)\) 的时间复杂度内求出该局面是否先手必胜。
但是,这样的时间复杂度实在太高。有没有什么巧妙而快速的方法呢?
定义 NIM 和为 \(a_1\oplus a_2\oplus\cdots\oplus a_n\)。
当且仅当 NIM 和为 \(0\) 时,该状态为必败状态;否则该状态为必胜状态。
证明
为什么异或值会和状态的胜负有关?下面给出了这个定理的证明过程。
为了证明该定理,只需要证明下面三个定理:
-
没有后继状态的状态是必败状态;
-
对于 \(a_1\oplus a_2\oplus\cdots\oplus a_n\not=0\) 的局面,一定存在某种移动使得 \(a_1\oplus a_2\oplus\cdots\oplus a_n=0\);
-
对于 \(a_1\oplus a_2\oplus\cdots\oplus a_n=0\) 的局面,一定不存在某种移动使得 \(a_1\oplus a_2\oplus\cdots\oplus a_n=0\)。
对于定理 \(1\),没有后继状态的状态只有一个,即全 \(0\) 局面。此时 \(a_1\oplus a_2\oplus\cdots\oplus a_n=0\)。
对于定理 \(2\),不妨假设 \(a_1\oplus a_2\oplus\cdots\oplus a_n=k\not=0\)。如果我们要将 \(a_i\) 改为 \(a_i'\),则 \(a_i'=a_i\oplus k\)。
根据异或定义,一定有奇数个 \(a_i\) 在 \(k\) 在二进制下的最高位为 \(1\)。
满足这个条件的 \(a_i\) 一定也满足 \(a_i>a_i\oplus k\),因而这也是个合法的移动。
对于定理 \(3\),如果我们要将 \(a_i\) 改为 \(a_i'\),则根据异或运算律可以得出 \(a_i=a_i'\),因而这不是个合法的移动。
四. 有向图游戏与 SG 函数
有向图游戏是一个经典的博弈游戏——实际上,大部分的公平组合游戏都可以转换为有向图游戏。
在一个有向无环图中,只有一个起点,上面有一个棋子,两个玩家轮流沿着有向边推动棋子,不能走的玩家判负。
定义 \(\operatorname{mex}\) 函数的值为不属于集合 \(S\) 中的最小非负整数,即:
\[\operatorname{mex}(S)=\min\{x\}(x\not\in S,x\in N) \]例如 \(\operatorname{mex}(\{0,2,4\})=1\),\(\operatorname{mex}(\{1,2\})=0\)。
对于状态 \(x\) 和它的所有 \(k\) 个后继状态 \(y_1,y_2,\cdots,y_n\),定义 \(\operatorname{SG}\) 函数:
\[\operatorname{SG}(x)=\operatorname{mex}(\{\operatorname{SG}(y_1),\operatorname{SG}(y_2),\cdots,\operatorname{SG}(y_n)\}) \]而对于由 \(n\) 个有向图游戏组成的组合游戏,设它们的起点分别为 \(s_1,s_2,\cdots,s_n\),则有定理:
当且仅当 \(\operatorname{SG}(s_1)\oplus\operatorname{SG}(s_2)\oplus\cdots\oplus\operatorname{SG}(s_n)\not=0\) 时,这个游戏是先手必胜的。同时,这是这一个组合游戏的游戏状态 \(x\) 的 SG 值。
这一定理被称作 Sprague-Grundy 定理(Sprague-Grundy Theorem), 简称 SG 定理。
五. 例题
套模板即可。
代码实现:
#include<map>
#include<set>
#include<list>
#include<cmath>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<cctype>
#include<cstdio>
#include<random>
#include<string>
#include<vector>
#include<climits>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<algorithm>
// #pragma GCC optmize("Ofast")
#define ll long long
#define ull unsigned long long
#define swap(a,b) a^=b^=a^=b
#define sprep(x,v) for(auto x:v)
#define rep(i,a,b) for(int i(a);i<=(b);++i)
#define per(i,a,b) for(int i(a);i>=(b);--i)
#define isdight(c) (c>='0'&&c<='9')
#define todight(c) (c^'0')
#define lowbit(x) (x&(~x+1))
#define sqr(x) ((x)*(x))
#define relax(a,b) a=max(a,b)
#define tense(a,b) a=min(a,b)
#define inf 0x3f3f3f3f
#define eps 1e-8
#define pii pair<int,int>
#define st first
#define nd second
#define mkp make_pair
#define writeln(x) write(x),puts("")
#define writesp(x) write(x),putchar(' ')
inline int max(int a,int b) {return(a>b?a:b);}
inline int min(int a,int b) {return(a<b?a:b);}
template<class T>
inline T read()
{
T x(0),f(1);
char c(getchar());
while(!isdight(c))
f=(c^'-'?1:-1),c=getchar();
while(isdight(c))
x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x*f;
}
template<class T>
inline void write(T x)
{
if(x<0)
putchar('-'),x=~x+1;
T sta[35];
int top(0);
do
{
sta[top++]=x%10;
x/=10;
}while(x);
while(top)
putchar(sta[--top]+'0');
}
using namespace std;
int t=read<int>();
signed main()
{
while(t--)
{
int flag=0,n=read<int>();
while(n--)
flag^=read<int>();
puts(flag?"YES":"NO");
}
return 0;
}
六. 习题
标签:状态,博弈论,笔记,学习,cdots,oplus,include,operatorname,define 来源: https://www.cnblogs.com/cantorsort2919/p/16505154.html