题解 - P4055 [JSOI2009]游戏
hopeful_imitation · · 题解
P4055 [JSOI2009]游戏 题解
题意
在一个格点图上,两方轮流移动棋子一步,不能移到已走过的位置,移不动者输。问如何设定棋子的初始位置,使得后手必胜?
分析
将这题可以分类为一类 二分图博弈 问题。
为什么是二分图?
这是一个常用的转换技巧,在许多网络流题目中体现的更精湛。一般一个网格图按照国际象棋的染色方式,可以被染成一个二分图。
对于点
转换后要怎么做?
首先给出一个大家都知道的结论:在这个博弈中,如果初始在二分图的所有最大匹配上,那么先手必胜,反之后手必胜。
为什么我是对的?
先给出样例的拟图:
其中加粗的点为在任意最大匹配上的点。不妨先称为“粗点”,相对地有“细点”。
可以发现,从一个粗点出发(如
梳理一下这是怎么回事:只要先手在粗点,那么无论对方怎么走,永远可以再次走最大匹配。如果不可以,这就与最大匹配的“最大”矛盾了。
反之,如果在细点上,那么后手一定可以在一个粗点上出发,即后手必胜。
所以,题目所求的就转化为:求在一个二分图上,有哪些点不一定在最大匹配上。
只要求出这个细点集,空(也就是说,存在完美匹配,任意点皆为粗点)即输出 LOSE,非空输出 WIN 即可。
具体地怎么实现?
首先用任意可以解决二分图最大匹配问题的算法(匈牙利或 Dinic)都可以。此时我们知道,不在当前匹配的点必然是细点。
接下去,我们可以做一个 DFS。如果一个点
主要代码
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;
}