P1971 [NOI2011] 兔兔与蛋蛋游戏
首先,棋子的颜色分黑和白,是一个标准的二分图形式。
其次,我们对操作进行观察,易得出:一个空位到过的位置之后不会再走了(考虑操作步骤的奇偶性以及棋子的颜色)。
于是我们大胆想象:移动棋子等价于移动空格,既然一个空格走了就不会再回到原来的位置,那么只需要把一个位置看成一个点(空格为初始位置),问题就可以转化为二分图博弈的形式。
以下关于二分图博弈模板的内容摘自我的文章:
-
给出一张二分图,将一个棋子放在起点。两人轮流操作,每人允许移动一步,不能移到曾经到过的点。无法操作者输。
-
结论:若最大匹配的所有方案都包含起点,先手必胜;反之后手必胜。
判断方法:把整张图的最大匹配和去掉起点的图的最大匹配进行比较,相同则后手胜;反之先手胜。
-
证明:其余的连通块都走不到且不影响,只需要考虑起点所在的连通块。我们按照结论中的两种情况来考虑:
- 存在最大匹配的方案不包含起点。
那么有:必定存在一种最大匹配使得每一条路径都是交错路。
其中交错路的定义是从一个未匹配点出发,经历的路径依次为未匹配边、匹配边、未匹配边……以此类推。
那么先手一开始必然走一条未匹配边,后手只需要走匹配边。注意到最后一条边必定是匹配边(不然就形成增广路了,与”最大匹配“不符)。
故而后手走完先手没法走,后手必胜。
- 不存在最大匹配的方案不包含起点。
不难发现所有路径上的边也都是交错的,只不过起点是一个匹配点。
那么开头先手选择某一种方案,走匹配边。那后手必定只能走未匹配边,先手再选择匹配边……以此类推。
最后一条必定为匹配边(否则与“所有方案都包含起点”矛盾),先手必胜。
#include <bits/stdc++.h>
#define FL(i, a, b) for(int i = (a); i <= (b); i++)
#define FR(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
const int N = 2010, M = 45;
int n, m, ans, k, s, vis[N], match[N], b[N], w[N];
int f[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
char c[M][M]; vector<int> e[N];
int id(int x, int y){return (x - 1) * m + y;}
int dfs(int u){
for(int &v: e[u]){
if(vis[v] || b[v]) continue; vis[v] = 1;
if(!match[v] || dfs(match[v])){match[match[u] = v] = u; return 1;}
}
return 0;
}
int main(){
scanf("%d%d", &n, &m);
FL(i, 1, n) scanf("%s", c[i] + 1);
FL(i, 1, n) FL(j, 1, m){
if(c[i][j] == '.') s = id(i, j), c[i][j] = 'X';
FL(l, 0, 3){
int x = i + f[l][0], y = j + f[l][1];
if(x < 1 || x > n || y < 1 || y > m || c[i][j] == c[x][y]) continue;
e[id(i, j)].emplace_back(id(x, y));
}
}
FL(i, 1, n) FL(j, 1, m) if(c[i][j] == 'X')
memset(vis, 0, sizeof(vis)), dfs(id(i, j));
scanf("%d", &k), b[s] = 1;
FL(i, 1, k + k){
if(match[s]){
int u = match[s]; match[s] = match[u] = 0;
memset(vis, 0, sizeof(vis)), w[i] = !dfs(u);
}
int x, y; scanf("%d%d", &x, &y);
b[s = id(x, y)] = 1;
}
FL(i, 1, k) ans += (w[i + i] && w[i + i - 1]);
printf("%d\n", ans);
FL(i, 1, k) if(w[i + i] && w[i + i - 1]) printf("%d\n", i);
return 0;
}