题目描述
这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。
这个游戏是在一个 𝑛n 行 𝑚m 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。
每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:
- 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。
- 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。
第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第x行第y列中的棋子移进空格中”记为 𝑀(𝑥,𝑦)M(x,y)。
例如下面是三个游戏的例子。
最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。
注意:
- 两个格子相邻当且仅当它们有一条公共边。
- 兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。
输入格式
输入的第一行包含两个正整数 𝑛,𝑚n,m。
接下来 𝑛n 行描述初始棋盘。其中第 𝑖i 行包含 𝑚m 个字符,每个字符都是大写英文字母 X
、大写英文字母 O
或点号 .
之一,分别表示对应的棋盘格中有黑色棋子、有白色棋子和没有棋子。其中点号 .
恰好出现一次。
接下来一行包含一个整数 𝑘k(1≤𝑘≤10001≤k≤1000) ,表示兔兔和蛋蛋各进行了 𝑘k 次操作。
接下来 2𝑘2k 行描述一局游戏的过程。其中第 2𝑖−12i−1 行是兔兔的第 𝑖i 次操作(编号为 𝑖i 的操作) ,第 2𝑖2i 行是蛋蛋的第 𝑖i 次操作。每个操作使用两个整数 𝑥,𝑦x,y 来描述,表示将第 𝑥x 行第 𝑦y 列中的棋子移进空格中。
输入保证整个棋盘中只有一个格子没有棋子, 游戏过程中兔兔和蛋蛋的每个操作都是合法的,且最后蛋蛋获胜。
输出格式
输出文件的第一行包含一个整数 𝑟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
说明/提示
对于 100%100% 的数据,1≤𝑛≤401≤n≤40,1≤𝑚≤401≤m≤40,1≤𝑘≤10001≤k≤1000。
测试点编号 | 𝑛n | 𝑚m |
---|---|---|
1,21,2 | 𝑛=1n=1 | 1≤𝑚≤201≤m≤20 |
33 | 𝑛=3n=3 | 𝑚=4m=4 |
4,54,5 | 𝑛=4n=4 | 𝑚=4m=4 |
6,76,7 | 𝑛=4n=4 | 𝑚=5m=5 |
88 | 𝑛=3n=3 | 𝑚=7m=7 |
9∼149∼14 | 𝑛=2n=2 | 1≤𝑚≤401≤m≤40 |
15,1615,16 | 1≤𝑛≤161≤n≤16 | 1≤𝑚≤161≤m≤16 |
17∼2017∼20 | 1≤𝑛≤401≤n≤40 | 1≤𝑚≤401≤m≤40 |
Code:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 50, K = 1005;
int n, m, ans, cnt, tot, head[N*N];
char g[N][N];
int match[N*N], res[K*2], win[K*2];
bool vis[N*N], block[N*N], color[N][N];
int dx[4] = {1, 0, 0, -1};
int dy[4] = {0, 1, -1, 0};
struct node
{
int to, nxt;
}edge[N*N*2*4]; //每个点连4个方向,双向边
void addedge(int s, int e)
{
cnt++;
edge[cnt].to = e;
edge[cnt].nxt = head[s];
head[s] = cnt;
return ;
}
bool dfs(int x) //匈牙利算法
{
for(int i = head[x]; i != 0; i = edge[i].nxt)
{
int y = edge[i].to;
if(block[y] == true) //添加一行屏蔽已删掉的点
continue;
if(vis[y] == false)
{
vis[y] = true;
//如果y没有匹配 或者 y的匹配点match[y]能找到一个新的匹配
if(match[y] == 0 || dfs(match[y]) == true)
{
match[y] = x; //y的配对点是x
match[x] = y; //增加这行代码的原因是为了找最大匹配非必须点
return true;
}
}
}
return false;
}
int get_id(int x, int y)
{
return (x-1) * m + y;
}
bool check(int x, int y)
{
if(x < 1 || x > n || y < 1 || y > m || color[x][y] == false)
return false;
return true;
}
int main()
{
//输入
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> g[i][j]; //迷宫数组
//染色
int sx, sy;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
if(g[i][j] == 'O') //将白色的棋子染色
{
color[i][j] = true;
}
else if(g[i][j] == '.') //起点记录,当作黑色对待
{
sx = i;
sy = j;
}
}
//建图
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
if(color[i][j] == true) //白子跳过
continue;
int cur = get_id(i, j); //当前点编号
for(int k = 0; k < 4; k++)
{
int nx = i + dx[k];
int ny = j + dy[k];
if(check(nx, ny) == false)
continue;
int nxt = get_id(nx, ny);
addedge(cur, nxt); //建边
addedge(nxt, cur); //找最大匹配不需要反边,但是找最大匹配非必须点需要折返跑
}
}
//匈牙利, 目的是看起点是否落在最大匹配中
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
if(color[i][j] == false)
continue;
memset(vis, 0, sizeof(vis));
int cur = get_id(i, j);
if(dfs(cur) == true)
ans++;
}
//输入游戏过程
int k;
cin >> k;
for(int i = 1; i <= 2*k; i++)
{
int cur = get_id(sx, sy);
block[cur] = true; //删掉cur
if(match[cur] == 0) //棋子当前不在匹配中,那么下一步会率先走到最大匹配中,必败
win[i] = false;
else
{
int nxt = match[cur];
match[cur] = match[nxt] = 0; //删掉cur与 nxt的匹配关系
memset(vis, 0, sizeof(vis)); //跑匈牙利记得清0 vis
if(dfs(nxt) == true) //若nxt还能找到匹配,则cur不是最大匹配必须, 那么下一步率先走到最大匹配,必败
win[i] = false;
else
win[i] = true;
}
cin >> sx >> sy;
}
//处理输出
for(int i = 1; i <= k; i++)
{
if(win[2*i-1] == true && win[2*i] == true) //兔兔走之前是有必胜策略,走完蛋蛋有必胜策略
{
res[++tot] = i;
}
}
cout << tot << endl;
for(int i = 1; i <= tot; i++)
{
cout << res[i] << endl;
}
return 0;
}