NOI2011·洛谷·兔兔与蛋蛋游戏

初见安~这里是传送门:洛谷P1971 [NOI2011]兔兔与蛋蛋游戏

题解

这种题啊……就只能多悟,才能看到了就知道怎么做【bushi。

看题目的操作:每次交替移动黑白棋子。然后就可以想到二分图了【??!什么鬼

空白格子从初始到终点走的路径上的格子都是与之交换过的,并且黑白相间,该路径与以前走过的路径不可能有交点。转化到二分图则是:把黑格子的点放一边,白格子的点放一边,每次操作都是从一边移动到另一边。

上文是用二分图模拟这个过程,现在我们再来看如何判定。因为都采用最优策略,所以如果从一个点出发后最多可以走奇数条边,那么先手必胜,反之后手必胜。这个很好理解。那么换到这个题上,如果兔兔本来在的点有先手必胜策略,但操作过后让蛋蛋有了先手必胜策略,那么这一步就是错的。对于最大边数的判定我们可以用到二分图匹配。即我们先求出一个最大匹配,假设当前点是u,在最大匹配中,如果把u删掉后还能找到新的增广路来代替,那么u先手必败。很好理解——u是匹配点,那么路径应该是匹配边—非匹配边—匹配边.. 删去后:非匹配边—匹配边—...匹配边,最后一定是匹配边结尾【否则就有新的增广路了,同理一条非匹配边后也一定能找到一条匹配边走下一步】,也就是说u作为匹配点时是非匹配边结尾,那么边数就是偶数的,先手必败

综上——我们先求出该二分图的最大匹配,再按照实际的下棋顺序模拟过程,求出每一步时的点是否有必胜策略,再单独看兔兔走的点即可【因为要统计有多少步】

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 50
#define maxk 2005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

char a[maxn][maxn]; 
int n, m, mp[maxn][maxn], s, t, Q;

struct edge {int to, nxt;} e[9000000];
int head[maxn * maxn], k = 0;
void add(int u, int v) {e[k] = {v, head[u]}; head[u] = k++;}

int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int id[maxn][maxn], mat[maxn * maxn], ans[maxk], tot = 0;
bool vis[maxn * maxn], ban[maxn * maxn], win[maxk];
bool in(int x, int y) {return 0 < x && x <= n && 0 < y && y <= m;}
bool dfs(int u) {//匈牙利求增广路
	if(ban[u]) return false;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(vis[v] || ban[v]) continue;
		vis[v] = true;
		if(!mat[v] || dfs(mat[v])) {mat[u] = v, mat[v] = u; return true;}
	}
	return false;
}

signed main() {
	memset(head, -1, sizeof head);
	n = read(), m = read();
	for(int i = 1; i <= n; i++) scanf("%s", a[i] + 1);
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) {
		mp[i][j] = (a[i][j] == 'O'? 1 : 2);
		if(a[i][j] == '.') s = i, t = j;
		id[i][j] = m * (i - 1) + j;
	}
	
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) //连边
		for(int p = 0; p < 4; p++) {
			register int tx = i + dir[p][0], ty = j + dir[p][1];
			if(in(tx, ty) && mp[i][j] != mp[tx][ty]) add(id[i][j], id[tx][ty]), add(id[tx][ty], id[i][j]);
		}
		
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(mp[i][j] == 2)//求增广路
		memset(vis, 0, sizeof vis), dfs(id[i][j]);
		
	Q = read();
	for(int i = 1; i <= Q * 2; i++) {//模拟过程
		register int u = id[s][t], v; ban[u] = true;//走过的点不能考虑了
		if(mat[u]) {//如果是匹配点
			memset(vis, 0, sizeof vis); v = mat[u];
			mat[u] = mat[v] = 0;//删去这条匹配边
			win[i] = 1 ^ dfs(v);//这里可以注意一下
		}
		s = read(), t = read();
	}
	
	for(int i = 1; i <= Q; i++) if(win[i * 2 - 1] && win[i * 2]) ans[++tot] = i;
	printf("%d\n", tot);
	for(int i = 1; i <= tot; i++) printf("%d\n", ans[i]);
	return 0;
}

迎评:)
——End——

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值