★【二分图匹配】【博弈论】【NOI2011】兔兔和蛋蛋的游戏

【问题描述】
这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。
这个游戏是在一个 n 行 m 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,
其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。
每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:
    兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。
    蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。
第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第
x 行第 y 列中的棋子移进空格中”记为 M(x,y)。
例如下面是三个游戏的例子。


最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。
她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。
注意:
    两个格子相邻当且仅当它们有一条公共边。
    兔兔的操作是“犯错误” 的当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。
【输入格式】
从文件 game.in 中读入数据。
输入的第一行包含两个正整数 n、m。
接下来 n 行描述初始棋盘。其中第 i 行包含 m 个字符,每个字符都是大写英文字母"X"、大写英文字母"O"或点号"."之一,分别表示对应的棋盘格中有黑色棋子、有白色棋子和没有棋子。其中点号"."恰好出现一次。
接下来一行包含一个整数 k(1≤k≤1000) 表示兔兔和蛋蛋各进行了 k 次操作。
接下来 2k 行描述一局游戏的过程。
其中第 2i – 1 行是兔兔的第 i 次操作(编号为 i 的操作)第 2i 行是蛋蛋的第 i 次操作。
每个操作使用两个整数 x,y 来描述,表示将第 x 行第 y 列中的棋子移进空格中。
输入保证整个棋盘中只有一个格子没有棋子,游戏过程中兔兔和蛋蛋的每个操作都是合法的,且最后蛋蛋获胜。
【输出格式】
输出到文件 game.out 中。
输出文件的第一行包含一个整数 r,表示兔兔犯错误的总次数。
接下来 r 行按递增的顺序给出兔兔“犯错误”的操作编号。其中第 i 行包含一个整数 ai 表示兔兔第 i 个犯错误的操作是他在游戏中的第 ai 次操作。
【输入样例 1】
1 6
XO.OXO
1
1 2
1 1

【输出样例 1】
1
1

【输入样例 2】
3 3
XOX
O.O
XOX
4
2 3
1 3
1 2
1 1
2 1
3 1
3 2
3 3

【输出样例 2】
0
【输入样例 3】
4 4
OOXX
OXXO
OO.O
XXXO
2
3 2
2 2
1 2
1 3

【输出样例 3】
2
1
2
【样例说明】
样例 1 对应图一中的游戏过程。
样例 2 对应图三中的游戏过程。

【数据规模】
所有测试数据的范围和特点如下表所示

此题考察二分图匹配。

朴素算法是用博弈逐层扩展MIN-MAX局面来判断是否必胜。

但这样做肯定要超时。于是考虑其他算法。
我们可以将每次选手的操作看作空白位置的移动,那么可以证明空白的移动路径一定不存在自交(因为如果自交,则说明移动偶数后到了原位,与题目中要求的游戏规则矛盾)。
标记所有可以达到的点,进行黑白染色,通过求二分图匹配的方法来判断是否存在必胜策略,若空白点在最大匹配上,那么存在必胜策略,否则不存在必胜策略。
这样,对于每一局的改变,只需要将一些点标记为不可达即可。
Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>

const int maxN = 1610, maxR = 50;
const int maxK = 1010;

struct Edge
{
	int v; Edge *next; Edge() {}
	Edge(int v, Edge *next): v(v), next(next) {}
} *edge[maxN]; bool marked[maxN], ban[maxN];
int mp[maxR][maxR], ord[maxR][maxR];
int res[maxK], Link[maxN];
int tot, n, m, K, ans, x0, y0;

bool Find(int u)
{
	marked[u] = 1;
	for (Edge *p = edge[u]; p; p = p -> next)
	if (!marked[p -> v] && !ban[p -> v])
	{
		marked[p -> v] = 1;
		if (Link[p -> v] == -1 || Find(Link[p -> v]))
		{
			Link[p -> v] = u, Link[u] = p -> v;
			return 1;
		}
	}
	return 0;
}

inline bool win()
{
	int tmp = 0; ban[ord[x0][y0]] = 1;
	memset(Link, 0xff, sizeof Link);
	for (int i = 1; i < tot + 1; ++i)
	if (!ban[i] && Link[i] == -1) //一定要有Link[i]不存在。
	{
		memset(marked, 0, sizeof marked);
		if (Find(i)) ++tmp;
	}
	//先将空白点标记为不可走,求一次二分图匹配。
	ban[ord[x0][y0]] = 0;
	for (int i = 1; i < tot + 1; ++i)
	if (!ban[i] && Link[i] == -1) //
	{
		memset(marked, 0, sizeof marked);
		if (Find(i)) --tmp;
	}
	//再将空白点标记为可走,再求一次二分图匹配。
	return (bool)tmp;
	//若两次匹配数相同,则说明空白点在最大匹配上,必胜,否则不必胜。
}

int main()
{
	freopen("game.in", "r", stdin);
	freopen("game.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for (int i = 1; i < n + 1; ++i)
	{
		scanf("\n");
		for (int j = 1; j < m + 1; ++j)
		switch (getchar())
		{
		case 'O': mp[i][j] = 1; break;
		case 'X': mp[i][j] = -1; break;
		case '.': mp[i][j] = 0; x0 = i, y0 = j; break;
		}
	}
	ord[x0][y0] = ++tot; //
	for (int i = 1; i < n + 1; ++i)
	for (int j = 1; j < m + 1; ++j)
	if (((((i + j) & 1) == ((x0 + y0) & 1)) && mp[i][j] == -1) ||
		((((i + j) & 1) != ((x0 + y0) & 1)) && mp[i][j] == 1))
		ord[i][j] = ++tot; //由于后面一些操作,这里最好弄成1下标。
	for (int i = 1, u; i < n + 1; ++i)
	for (int j = 1, v; j < m + 1; ++j)
	if (u = ord[i][j])
	{
		if (i < n && (v = ord[i + 1][j]))
		{
			edge[u] = new Edge(v, edge[u]);
			edge[v] = new Edge(u, edge[v]);
		}
		if (j < m && (v = ord[i][j + 1]))
		{
			edge[u] = new Edge(v, edge[u]);
			edge[v] = new Edge(u, edge[v]);
		}
	}
	scanf("%d", &K);
	for (int i = 0; i < K; ++i)
	{
		bool tmp = win();
		ban[ord[x0][y0]] = 1;
		scanf("%d%d", &x0, &y0);
		if (tmp && win()) res[ans++] = i + 1;
		ban[ord[x0][y0]] = 1;
		scanf("%d%d", &x0, &y0);
	}
	printf("%d\n", ans);
	for (int i = 0; i < ans; ++i)
		printf("%d\n", res[i]);
	return 0;
}
附博弈骗分算法:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
#define check(i, j) \
((i) > 0 && (i) <= n && (j) > 0 && (j) <= m)
const int maxN = 50, maxK = 1010;
const int INF = 0x3f3f3f3f, MIN = -1, MAX = 1;
const int dx[] = {1, -1, 0, 0};
const int dy[] = {0, 0, -1, 1};
int mp[maxN][maxN], err[maxK], n, m, K, x0, y0, cnt;

inline int min(int a, int b) {return a < b ? a : b;}
inline int max(int a, int b) {return a > b ? a : b;}

int Dfs(int (*opt)(int, int))
{
	int ths = opt(MIN, MAX), ans = ~opt(INF, ~INF);
	for (int i = 0; i < 4; ++i)
	{
		int x = x0 + dx[i], y = y0 + dy[i];
		if (check(x, y) && mp[x][y] == ths)
		{
			std::swap(mp[x][y], mp[x0][y0]);
			std::swap(x, x0); std::swap(y, y0);
			int tmp = Dfs(opt(0, 1) ? min : max);
			ans = opt(ans, tmp);
			std::swap(x, x0); std::swap(y, y0);
			std::swap(mp[x][y], mp[x0][y0]);
			if (ans == opt(INF, ~INF)) return ans;
		}
	}
	return ans;
}

int main()
{
	freopen("game.in", "r", stdin);
	freopen("game.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for (int i = 1; i < n + 1; ++i)
	{
		scanf("\n");
		for (int j = 1; j < m + 1; ++j)
		switch (getchar())
		{
		case 'O': mp[i][j] = MAX; break;
		case 'X': mp[i][j] = MIN; break;
		case '.': mp[i][j] = 0, x0 = i, y0 = j; break;
		}
	}
	scanf("%d\n", &K);
	for (int i = 0; i < K << 1; ++++i)
	{
		int x, y; scanf("%d%d", &x, &y);
		if (Dfs(max) == INF) err[i >> 1] = 1;
		std::swap(mp[x][y], mp[x0][y0]);
		x0 = x, y0 = y;
		if (err[i >> 1] && Dfs(min) > ~INF)
			err[i >> 1] = 0;
		scanf("%d%d", &x, &y);
		std::swap(mp[x][y], mp[x0][y0]);
		x0 = x, y0 = y;
		if (err[i >> 1]) ++cnt;
	}
	printf("%d\n", cnt);
	for (int i = 0; i < K; ++i)
		if (err[i]) printf("%d\n", i + 1);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值