摘自百度百科。
从目录中跳转。
巴什博弈(\(\mathcal{Bash\;game}\))
威佐夫博弈(\(\mathcal{Wythoff's\;game}\))
尼姆游戏(\(\mathcal{Nim\;Game}\))
SG 函数
双人博弈。
有一堆总数为 \(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\) 下的后手,按上述策略行动,则必获胜。
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;
}
}
双人博弈。
有两堆石子,两名玩家每次可以从任意一堆石子中取任意多的石子或者从两堆石子中取同样多的石子,最后取完者胜。
枚举观察可以发现,在遇到特定的局势时,先手必败,我们称其为奇异局势。
在 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\),满足即为奇异局势。
这道题卡精度,需要手写 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();}
双人博弈。
有若干堆石子,每堆石子的数量都是有限的,合法的移动是选择一堆石子并拿走若干大于零颗,最后取完者胜。
设一种局势的状态为 \(\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 局势(在结束时的状态先手的人显然无法有任何操作)。
那么通过推理,我们还可以得到两条定理:
一个状态为 N 状态当且仅当存在至少一个 P 状态为它的后继状态。
一个状态是 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\)。
若想得到该结论,只需证明一下两个定理:
对于 \(k \neq 0\) 的状态,存在至少一种后继状态使得 \(k =0\)。
对于 \(k=0\) 的状态,所有的后继状态的 \(k\) 值均不为 \(0\)。
来自 OI-wiki。
#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;
}
定义 mex 函数为不属于集合 S 中的最小自然数,即 \(mex(S)=min\{ x \} \left(x\notin S,x\in N\right)\)。
对于一个状态 x 和它的所有后继状态 \(y_1,y_2,\ldots , y_n\),定义 SG 函数为:
对于由 \(n\) 个有向图游戏组成的组合游戏,设它们的起点分别为 \(s_1,s_2,\ldots,s_n\),那么当且仅当 \(SG(s_1)\oplus SG(s_2)\oplus\ldots\oplus SG(s_n)\neq 0\) 时,先手必胜。这就是 SG 定理。
摘自 OI-wiki。
很基础的 SG 函数应用题,只需要求出整棵树的 SG 值即可。
但真的只是用求所有子树的 SG 值异或和吗?
仔细观察,这道题其实可以转化为树上的关于边的尼姆游戏,每一棵子树到根还有一条未被算入的边,因此正确的结果应该为:
#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();}
可以显然看出这是一个用 SG 函数求解的问题,显然我们只需对每个连通块计算一遍其 SG 值异或起来检验是否非零即可。
这道题删的边是与根节点联通的边,也就是删边操作后会形成森林,这些森林也就是每个状态的后继。我们发现,不同的树之间的操作互不影响,将它们看做不同的游戏,那么该后继状态的 SG 值其实就是每一个游戏的 SG 值的异或和。
根据 SG 定理,我们的任务转化成了快速求出所有树的 SG 值的mex 值。
考虑用 0-1trie 维护,可以达到 \(\mathcal{O(n\,logn)}\) 的复杂度。