题解 - Luogu P4055 [JSOI2009]游戏

题意

在一个格点图上,两方轮流移动棋子一步,不能移到已走过的位置,移不动者输。问如何设定棋子的初始位置,使得后手必胜?

分析

将这题可以分类为一类 二分图博弈 问题。

为什么是二分图?

这是一个常用的转换技巧,在许多网络流题目中体现的更精湛。一般一个网格图按照国际象棋的染色方式,可以被染成一个二分图。

草率的染色图。

对于点 (x,y)(x,y),颜色就是 x+yx+y 的奇偶。

转换后要怎么做?

首先给出一个大家都知道的结论:在这个博弈中,如果初始在二分图的所有最大匹配上,那么先手必胜,反之后手必胜。

为什么我是对的?

先给出样例的拟图:

草率的二分图。

其中加粗的点为在任意最大匹配上的点。不妨先称为“粗点”,相对地有“细点”。

可以发现,从一个粗点出发(如 (2,2)(2,2)),只需要任意选择一个在任意一个最大匹配上的匹配点((2,3)(2,3)(3,2)(3,2))即可。只要先手走的是最大匹配,无论后手怎么走,因为题目要求不能回头,所以一定可以继续沿着一个最大匹配走。

梳理一下这是怎么回事:只要先手在粗点,那么无论对方怎么走,永远可以再次走最大匹配。如果不可以,这就与最大匹配的“最大”矛盾了。

反之,如果在细点上,那么后手一定可以在一个粗点上出发,即后手必胜。

所以,题目所求的就转化为:求在一个二分图上,有哪些点不一定在最大匹配上。

只要求出这个细点集,空(也就是说,存在完美匹配,任意点皆为粗点)即输出 LOSE,非空输出 WIN 即可。

具体地怎么实现?

首先用任意可以解决二分图最大匹配问题的算法(匈牙利或 Dinic)都可以。此时我们知道,不在当前匹配的点必然是细点。

接下去,我们可以做一个 DFS。如果一个点 PP 已被证实是细点,设它有一边连着 QQ,且 QQ 在最大匹配上,则 QQ 的匹配点也是细点。如样例,如果当前 (2,2)(2,2) 匹配 (2,3)(2,3),当我们从 (3,2)(3,2) 开始 DFS 时,就会发现 (2,3)(2,3) 是细点。想想看,将 (2,2)(2,3)(2,2)\Rightarrow(2,3) 换成 (2,2)(3,2)(2,2)\Rightarrow(3,2) 都是最大匹配。根据这样的性质,找出所有的细点即可。

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
constexpr int MXN = 101;
constexpr int MXPTS = 10001;

bool enable[MXPTS];
int n, m, pts;

inline int id(int x, int y) { return x * m + y; }

int head[MXPTS], to[MXPTS << 2], nxt[MXPTS << 2], es; // for base graph
int link[MXPTS], vis[MXPTS]; // for bi-graph
void init() {
fill(head, head + pts, -1);
fill(link, link + pts, -1);
fill(vis, vis + pts, -1);
}
void addedge(int f, int t) {
to[es] = t;
nxt[es] = head[f];
head[f] = es++;
}
bool ask(int cur, int t) {
if (vis[cur] == t) return false;
vis[cur] = t;
for (int i(head[cur]); ~i; i = nxt[i]) {
if (!~link[to[i]] || ask(link[to[i]], t)) {
link[to[i]] = cur;
return true;
}
}
return false;
}
void makepair() {
for (int i(0); i != n; ++i) {
for (int j(0); j != m; ++j) {
if (!((i + j) & 1) && enable[id(i, j)]) ask(id(i, j), id(i, j));
}
}
for (int i(0); i != pts; ++i) {
if (~link[i]) link[link[i]] = i;
}
}

bool cango[MXPTS], ans;

void findfake(int cur) {
ans = cango[cur] = true;
for (int i(head[cur]); ~i; i = nxt[i]) {
if (~link[to[i]] && !cango[link[to[i]]]) findfake(link[to[i]]);
}
}

int main() {
cin >> n >> m;
pts = n * m;
init();
for (int i(0); i != pts; ++i) {
char c;
cin >> c;
if (c == '.') enable[i] = true;
}
for (int i(0); i != n; ++i) {
for (int j(0); j != m; ++j) {
if (enable[id(i, j)]) {
if (i != n - 1 && enable[id(i + 1, j)])
addedge(id(i, j), id(i + 1, j)), addedge(id(i + 1, j), id(i, j));
if (j != m - 1 && enable[id(i, j + 1)])
addedge(id(i, j), id(i, j + 1)), addedge(id(i, j + 1), id(i, j));
}
}
}
makepair();
for (int i(0); i != pts; ++i) {
if (enable[i] && !~link[i]) {
findfake(i);
}
}
puts(ans ? "WIN" : "LOSE");
for (int i(0); i != n; ++i) {
for (int j(0); j != m; ++j) {
if (cango[id(i, j)]) cout << i + 1 << ' ' << j + 1 << endl;
}
}
return 0;
}

题解 - Luogu P4055 [JSOI2009]游戏
http://sunsetglow95.github.io/2022/03/26/sol-LGP4055/
作者
SunsetGlow95
发布于
2022年3月26日
许可协议