[算法]博弈论
一、有向图游戏
所谓有向图游戏,就是在一个有向无环图中,只有一个起点,上面有一个棋子,两个玩家轮流沿着有向边推动棋子,不能走的玩家判负。可以看出,这个游戏必须会终止。
定义必败状态为先手必败状态,必胜状态为先手必胜状态。
我们可以得到下面三条定理:
定理一:没有后继状态的状态必是必败状态。
定理二:必胜状态必有某个后继状态为必败状态。
定理三:必败状态的后继状态如果有,那么必然全是必胜状态。
二、SG函数
定义
m
e
x
mex
mex函数为不属于集合
S
S
S中的最小非负整数。例如
m
e
x
(
{
0
,
2
,
4
}
)
=
1
,
m
e
x
(
ϕ
)
=
0
mex\left(\{ 0,2,4\} \right)=1,mex(\phi)=0
mex({0,2,4})=1,mex(ϕ)=0
对于状态
x
x
x以及其所有
k
k
k个后继状态
y
1
,
y
2
,
…
,
y
k
y_1,y_2,\dots,y_k
y1,y2,…,yk,定义SG函数:
S
G
(
x
)
=
m
e
x
(
S
G
(
y
1
)
,
S
G
(
y
2
)
,
…
,
S
G
(
y
k
)
)
SG(x) = mex(SG(y_1),SG(y_2),\dots,SG(y_k))
SG(x)=mex(SG(y1),SG(y2),…,SG(yk))
对于终止状态
x
x
x,定义
S
G
(
x
)
=
0
SG(x)=0
SG(x)=0.
而对于由
n
n
n个有向图游戏组成的组合游戏,设它们的起点分别为
s
1
,
s
2
,
…
,
s
n
s_1,s_2,\dots,s_n
s1,s2,…,sn,则有定理:当且仅当
S
G
(
s
1
)
⨁
S
G
(
s
2
)
⨁
⋯
⨁
S
G
(
s
n
)
≠
0
SG(s_1)\bigoplus SG(s_2)\bigoplus \dots \bigoplus SG(s_n)\ne 0
SG(s1)⨁SG(s2)⨁⋯⨁SG(sn)=0时,这个游戏是先手必胜的。
令
k
=
S
G
(
s
1
)
⨁
S
G
(
s
2
)
⨁
⋯
⨁
S
G
(
s
n
)
k=SG(s_1)\bigoplus SG(s_2)\bigoplus \dots \bigoplus SG(s_n)
k=SG(s1)⨁SG(s2)⨁⋯⨁SG(sn)
我们证明,当
k
=
0
k=0
k=0时为必败状态,
k
≠
0
k\ne 0
k=0时为必胜状态。
对于定理一,显然有
0
⨁
0
⨁
⋯
⨁
0
=
0
0\bigoplus 0\bigoplus \dots \bigoplus 0= 0
0⨁0⨁⋯⨁0=0
对于定理二,
S
G
(
s
1
)
⨁
S
G
(
s
2
)
⋯
S
G
(
s
n
)
≠
0
SG(s_1)\bigoplus SG(s_2)\cdots SG(s_n)\ne 0
SG(s1)⨁SG(s2)⋯SG(sn)=0的时候,必存在某种操作,使得改变某个
a
i
a_i
ai为
a
i
′
a_i'
ai′后,必有
a
1
⨁
a
2
⨁
⋯
⨁
a
i
⋯
⨁
a
n
=
0
a_1\bigoplus a_2 \bigoplus \dots \bigoplus a_i \dots \bigoplus a_n = 0
a1⨁a2⨁⋯⨁ai⋯⨁an=0。
根据异或定义,可得
a
i
′
=
a
i
⨁
k
a_i'=a_i\bigoplus k
ai′=ai⨁k,那么必有奇数个
a
i
a_i
ai在
k
k
k的二进制最高位为
1
1
1。我们取出这些
a
i
a_i
ai中的每一个,然后我们可以发现必有
a
i
>
a
i
⨁
k
a_i>a_i\bigoplus k
ai>ai⨁k,因而这是个合法的移动。
对于定理三,可以发现如果这个操作合法,那么必有
a
i
=
a
i
′
a_i=a_i'
ai=ai′,矛盾!
三、Nim游戏
n
n
n堆物品,每堆有
a
i
a_i
ai个,两个玩家轮流取走任意一堆的任意个物品,但不能不取。
取走最后一个物品的人获胜。
我们发现每堆石子是独立的,然后
S
G
(
a
i
)
=
a
i
SG(a_i)=a_i
SG(ai)=ai(用数学归纳法不难得出),于是我们只需要求出
a
1
⨁
a
2
⨁
⋯
⨁
a
n
a_1\bigoplus a_2 \bigoplus \dots \bigoplus a_n
a1⨁a2⨁⋯⨁an的值即可。
四、拓展Nim游戏
集合-Nim游戏
每次可以拿走的石子的个数只能是集合
{
s
1
,
s
2
,
…
,
s
k
}
\{s_1,s_2,\dots,s_k \}
{s1,s2,…,sk}中的某个数。
我们直接记忆化搜索即可,实现
m
e
x
mex
mex函数的时候注意用
s
e
t
set
set判重。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 110;
int n,k;
int s[maxn],h[maxn];
int f[10010];
inline int sg(int x){
if(f[x] != -1) return f[x];
unordered_set<int> tmp;//子节点的sg函数值可能会有重复,所以用集合去重
for(int i = 1; i <= k && x >= s[i]; i++){
tmp.insert(sg(x-s[i]));
}
for(int i = 0;; i++) if(!tmp.count(i)) return f[x] = i;
}
void solve(){
memset(f, -1, sizeof f);
scanf("%d",&k);
for(int i = 1; i <= k; i++) scanf("%d",&s[i]);
sort(s+1,s+k+1);
for(int i = 1 ; i <= k; i++) cout<<s[i]<<endl;
scanf("%d",&n);
int res = 0;
for(int i = 1; i <= n; i++){
scanf("%d",&h[i]);
res ^= sg(h[i]);
}
if(res) puts("Yes");
else puts("No");
}
int main()
{
solve();
return 0;
}
拆分-Nim游戏
每次把一堆全部拿走,然后替换成规模更小的两堆(最小规模为0)。
显然这个游戏会终止,并且是个有向图游戏。
对于状态
x
x
x,它的后继状态是两个堆,并且互相独立,所以我们可以求出两个堆的
S
G
SG
SG值,然后异或起来。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 1e6 + 10;
int n,x;
int f[110];
inline int sg(int x){
if(f[x] != -1) return f[x];
unordered_set<int> tmp;
for(int i = 0; i < x; i++)
for(int j = 0; j < x; j++)
tmp.insert(sg(i)^sg(j));
for(int i = 0; ;i++)
if(!tmp.count(i)) return f[x] = i;
}
void solve(){
scanf("%d",&n);
int ans = 0;
memset(f,-1,sizeof f);
for(int i = 1; i <= n; i++){
scanf("%d",&x);
ans ^= sg(x);
}
if(ans) puts("Yes");
else puts("No");
}
int main()
{
solve();
return 0;
}