@bzoj - 1443@ [JSOI2009]游戏Game


@description@

在N*M的迷宫中有一个棋子,小AA首先任意选择棋子放置的位置。然后,小YY和小AA轮流将棋子移动到相邻的格子里。
游戏的规则规定,在一次游戏中,同一个格子不能进入两次,且不能将棋子移动到某些格子中去。
当玩家无法继续移动棋子时,游戏结束,最后一个移动棋子的玩家赢得了游戏。

例如下图所示的迷宫,迷宫中”。”表示棋子可以经过的格子,而”#”表示棋子不可以经过的格子:
.##
...
#.#
若小AA将棋子放置在(1,1),则小AA则无论如何都无法赢得游戏。
而若小AA将棋子放置在(3,2)或(2,3),则小AA能够赢得游戏。例如,小AA将棋子放置在(3,2),小YY只能将它移动到(2,2),此时小AA再将棋子移动到(2,3),就赢得了游戏。

小AA和小YY都是绝顶聪明的小朋友,且从不失误。小AA到底能不能赢得这场游戏?

Input
输入数据首先输入两个整数N,M,表示了迷宫的边长。 接下来N行,每行M个字符,描述了迷宫。

Output
若小AA能够赢得游戏,则输出一行"WIN",然后输出所有可以赢得游戏的起始位置,按行优先顺序输出 每行一个,否则输出一行"LOSE"(不包含引号)。

Sample Input
3 3
.##
...
#.#
Sample Output
WIN
2 3
3 2
HINT
对于100%的数据,有1≤n,m≤100。

@solution@

经典题目.jpg。

首先棋盘是一个经典的二分图,通过黑白染色把二分图搞出来过后,相当于是问从每一个结点出发,后手是否有必胜策略。
假如从左边出发,先手一定是左 -> 右,后手一定是右 -> 左。发现往返的这个过程好像有点像增广路。

如果后手必胜,则最终的路径长度一定为偶数。
联系到增广路的话,假如一开始的边为非匹配边,交替走匹配边与非匹配边,并且最终路径长度如果为奇数,则一定可以扩大匹配的数量。
如果当前已经是最大匹配了,匹配数量不可能扩大,则可以反推出最终路径长度一定为偶数。
如果一个点不在某一个最大匹配中,则它所连的全部边都为非匹配边。先手无论选择哪一条边,只要后手一直沿匹配边走,都是后手必胜。

反过来,如果一个点在所有最大匹配中,如果先手选择一直走匹配边一定必胜。
因为假如最后得到的路径长度为偶数,则可以将这条路径的匹配 -> 非匹配,非匹配 -> 匹配,得到的依然是最大匹配且起点不在最大匹配中,矛盾。
所以最后得到的路径长度一定为奇数,也就是说先手必胜。

@accepted code@

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 100*100;
const int dx[] = {1, -1, 0, 0};
const int dy[] = {0, 0, 1, -1};
typedef pair<int, int> pii;
vector<pii>ans;
void print() {
	if( ans.empty() ) puts("LOSE");
	else {
		puts("WIN");
		for(int i=0;i<(int)ans.size();i++)
			printf("%d %d\n", ans[i].first, ans[i].second);
	}
}
int lnk[MAXN + 5]; bool vis[MAXN + 5];
vector<int>G[MAXN + 5], v[2];
void clear(int t) {
	for(int i=0;i<v[t].size();i++)
		vis[v[t][i]] = false;
}
bool dfs(int x) {
	for(int i=0;i<G[x].size();i++) {
		int t = G[x][i];
		if( vis[t] ) continue;
		vis[t] = true;
		if( !lnk[t] || dfs(lnk[t]) ) {
			lnk[t] = x, lnk[x] = t;
			return true;
		}
	}
	return false;
}
bool check(int x) {
	for(int i=0;i<G[x].size();i++) {
		int t = G[x][i];
		if( vis[t] ) continue;
		vis[t] = true;
		if( !lnk[t] || check(lnk[t]) )
			return true;
	}
	return false;
}
int id[100 + 5][100 + 5], cnt;
char s[100 + 5][100 + 5];
int main() {
	int N, M; scanf("%d%d", &N, &M);
	for(int i=1;i<=N;i++)
		scanf("%s", s[i] + 1);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			if( s[i][j] == '.' )
				id[i][j] = (++cnt);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++) {
			if( s[i][j] == '.' ) {
				for(int k=0;k<4;k++) {
					int x = i + dx[k], y = j + dy[k];
					if( x < 1 || y < 1 || x > N || y > M ) continue;
					if( s[x][y] == '#' ) continue;
					G[id[i][j]].push_back(id[x][y]);
				}
				v[(i+j)&1].push_back(id[i][j]);
			}
		}
	for(int i=0;i<v[0].size();i++)
		clear(1), dfs(v[0][i]);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			if( s[i][j] == '.' ) {
				if( !lnk[id[i][j]] )
					ans.push_back(make_pair(i, j));
				else {
					clear((i+j)&1), vis[id[i][j]] = true;
					if( check(lnk[id[i][j]]) )
						ans.push_back(make_pair(i, j));
				}
			}
	print();
}

@details@

为了偷懒写了个每次删点重新增广的算法。。。
其实比较快的应该是用网络流 + 连通性判一个点是否存在于所有最大匹配中。

posted @ 2019-11-13 09:10  Tiw_Air_OAO  阅读(176)  评论(0编辑  收藏  举报