背景介绍
给定 n n n 堆物品,第 i i i 堆物品有 A i A_i Ai 个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但是不能不取。取走最后一件物品者获胜。两个人都采取最优策略,问先手是否能必胜
这种游戏叫做NIM博弈,NIM博弈不存在平局,只有先手必胜和先手必败的两种情况。
定理:NUM博弈先手必胜,当且仅当
A
1
x
o
r
A
2
x
o
r
.
.
.
x
o
r
A
n
!
=
0
A_1 \ xor \ A_2 \ xor \ ...\ xor\ A_n != 0
A1 xor A2 xor ... xor An!=0
简单证明:显然,所有物品取光是必败态,此时 A 1 x o r A 2 x o r . . . x o r A n = 0 A_1 \ xor \ A_2 \ xor \ ...\ xor\ A_n = 0 A1 xor A2 xor ... xor An=0,当 A 1 x o r A 2 x o r . . . x o r A n ! = 0 A_1 \ xor \ A_2 \ xor \ ...\ xor\ A_n != 0 A1 xor A2 xor ... xor An!=0由于每次只能从一堆中选取物品,所以我们总是能取出一定量的石子使得 A 1 x o r A 2 x o r . . . x o r A n = 0 A_1 \ xor \ A_2 \ xor \ ...\ xor\ A_n = 0 A1 xor A2 xor ... xor An=0,让对手面临这样的状态,所以我们必然能够胜利。
有向图游戏
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边移动,每次可以移动一步,无法移动着判负。该游戏被称为有向图游戏。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面中沿着合法行动能够到达的下一个局面连有向边。
Mex运算
设
S
S
S 是一个非负整数集合。定义
m
e
x
(
S
)
mex(S)
mex(S) 为求出不属于集合
S
S
S 的最小非负整数,即
m
e
x
(
S
)
=
min
x
∈
N
,
x
∉
S
{
x
}
mex(S)\ =\ \min_{x\in N,\ x\notin S} \left\{ x\right\}
mex(S) = x∈N, x∈/Smin{x}
SG函数
在有向图游戏中,对于每个节点
x
x
x,设从
x
x
x 出发共有
k
k
k 条有向边,分别达到节点
y
1
,
y
2
,
.
.
.
,
y
k
y_1, y_2, ... , y_k
y1,y2,...,yk,定义
S
G
(
x
)
SG(x)
SG(x) 为
x
x
x 的后继节点
y
1
,
y
2
,
.
.
.
,
y
k
y1, y2, ... , y_k
y1,y2,...,yk 的
S
G
SG
SG 函数值构成的集合再执行mex运算的结果,即
S
G
(
x
)
=
m
e
x
(
{
S
G
(
y
1
)
,
S
G
(
y
2
)
,
.
.
.
,
S
G
(
y
k
)
}
)
SG(x)\ =\ mex(\{ SG(y1),\ SG\left( y2\right) ,\ ...\ ,\ SG\left( yk\right) \} )
SG(x) = mex({SG(y1), SG(y2), ... , SG(yk)})
整个有向图游戏
G
G
G 的
S
G
SG
SG 函数值被定义为有向图游戏起点
s
s
s 的
S
G
SG
SG函数值,即
S
G
(
G
)
=
S
G
(
s
)
SG(G) = SG(s)
SG(G)=SG(s)
有向图游戏的和
设
G
1
,
G
2
,
.
.
.
,
G
m
G_1, G_2, ... , G_m
G1,G2,...,Gm 是
m
m
m 个有向图游戏。定义有向图游戏
G
G
G , 它的行动规则是任选某个有向图游戏
G
i
G_i
Gi,并在
G
i
G_i
Gi 上行动一步。
G
G
G 被称为有向图游戏
G
1
,
G
2
,
.
.
.
,
G
m
G_1, G_2, ... , G_m
G1,G2,...,Gm 的和。
有向图游戏的和的
S
G
SG
SG 函数值等于它包含的各个子游戏
S
G
SG
SG 函数值的异或和,即:
S
G
(
G
)
=
S
G
(
G
1
)
x
o
r
S
G
(
G
2
)
x
o
r
.
.
.
x
o
r
S
G
(
G
m
)
SG(G) = SG(G_1)\ xor \ SG(G_2)\ xor \ ... \ xor \ SG(G_m)
SG(G)=SG(G1) xor SG(G2) xor ... xor SG(Gm)
定理
必败点
S
G
SG
SG函数的值等于0
必胜点
S
G
SG
SG函数的值大于0
例题
题目大意
给定一张 N×M 的矩形网格纸,两名玩家轮流行动。
在每一次行动中,可以任选一张矩形网格纸,沿着某一行或某一列的格线,把它剪成两部分。
首先剪出 1×1 的格纸的玩家获胜。
两名玩家都采取最优策略行动,求先手是否能获胜。
提示:开始时只有一张纸可以进行裁剪,随着游戏进行,纸张被裁剪成 2,3,… 更多张,可选择进行裁剪的纸张就会越来越多。
解题思路
显然没有玩家会剪出
1
∗
X
1*X
1∗X 或者
X
∗
1
X*1
X∗1 的情况,在这样的条件下,要想达到 1*1 的网格纸,就必须经历 2*2、3*2、2*3这三种必败态之一,我们把这三种状态作为终止状态。每一次剪纸,会出现两种新的“子剪纸游戏”,我们对者两者的
S
G
SG
SG 值进行
x
o
r
xor
xor 运算,可以得到这个行动面临的
S
G
SG
SG 值,所以我们可以得出递推关系
S
G
(
N
,
M
)
=
m
e
x
(
{
S
G
(
i
,
M
)
x
o
r
S
G
(
N
−
i
,
M
)
,
1
⩽
i
<
N
}
∪
{
S
G
(
N
,
i
)
x
o
r
S
G
(
N
,
M
−
i
)
,
1
⩽
i
<
M
}
)
SG(N,M)\ =\ mex(\left\{ SG\left( i,\ M\right) \ xor\ SG\left( N-i,\ M\right) ,\ 1\ \leqslant \ i\ <\ N\right\} \ \cup \ \left\{ SG\left( N,\ i\right) \ xor\ SG\left( N,\ M-i\right) ,\ 1\ \leqslant \ i\ <\ M\right\} )
SG(N,M) = mex({SG(i, M) xor SG(N−i, M), 1 ⩽ i < N} ∪ {SG(N, i) xor SG(N, M−i), 1 ⩽ i < M})
我们还要注意的一点是,求解的过程中不能先打出 S G SG SG 表,如果我们打了一个200*200的表,但是在询问的过程中, s g [ N ] [ M ] sg[N][M] sg[N][M] 会被 s g [ 200 − N ] [ 200 − M ] sg[200-N][200-M] sg[200−N][200−M] 所限制,所以我们每次递归求 S G [ N ] [ M ] SG[N][M] SG[N][M] 即可
Code
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 207;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int sg[207][207];
int n, m;
int solve(int x, int y){
if(sg[x][y] != -1)
return sg[x][y];
vector<int> v(MAXN);
for(int i = 2; x - i >= 2; i++)
v[solve(i, y) ^ solve(x-i, y)]++;
for(int i = 2; y - i >= 2; i++)
v[solve(x, i) ^ solve(x, y-i)]++;
int p = 0;
while(v[p]) p++;
return sg[x][y] = p;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
qc;
int T;
for(int i = 1; i < MAXN; i++)
for(int j = 1; j < MAXN; j++)
sg[i][j] = -1;
sg[2][2] = sg[2][3] = sg[3][2] = 0;
T = 1;
while(T--){
while(cin >> n >> m)
if(solve(n, m))
cout << "WIN" << endl;
else
cout << "LOSE" << endl;
}
return 0;
}