题目链接:POJ - 3279
题目描述
给你一个
01
01
01 矩阵,矩阵大小为
M
∗
N
M * N
M∗N。(
1
<
=
M
,
N
<
=
15
1 <= M , N <= 15
1<=M,N<=15)
每次操作选择一个格子,使得该格子与上下左右四个格子的值翻转。
至少多少次操作可以使得矩阵中所有的值变为
0
0
0 ?
请输出翻转方案,若没有方案,输出 IMPOSSIBLE
。
分析
算法:暴力枚举
road[N][N]
:存放原数据的数组。
backup[N][N]
:临时存放 road
的数组。
ans[N][N]
:答案数组。
turn[N][N]
:每个点的翻转情况。1
表示翻转,0
表示不翻转。
dx[], dy[]
:偏移量数组。
如果第一行的翻转情况固定,那么可以唯一对应一种翻转方案,只需要检验最后一行是否满足全为 0
即可。
- 证明:假设当前点是
road[i][j]
且i != 1
,若road[i - 1][j] == 1
,即上一行的这个位置是1
,那么road[i][j]
这个点必定翻转。也即,如果第一行的翻转状态被确定,那么第二行的也被确定,以此类推,最后一行的也被确定。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 15 + 5, INF = 0x3f3f3f3f;
int n, m;
int road[N][N];
int turn[N][N];
int ans[N][N];
int backup[N][N];
int dx[5] = {0, 0, 1, 0, -1};
int dy[5] = {0, 1, 0, -1, 0};
int solve()
{
int cnt = 0;
memcpy(backup, road, sizeof road); // 备份road
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
if (backup[i - 1][j]) turn[i][j] = 1;
if (turn[i][j])
{
cnt ++ ;
// 将对应的联通块翻转
for (int k = 0; k < 5; k ++ )
{
int x = i + dx[k];
int y = j + dy[k];
backup[x][y] = !backup[x][y];
}
}
}
for (int i = 1; i <= m; i ++ )
if (backup[n][i]) // 如果最后一行有1,不合理的方案。
return -1;
return cnt;
}
int main()
{
// 读入数据
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cin >> road[i][j];
// cnt初始为无穷大,如果遇到可以更新的值,将cnt更新,同时记录方案。
int cnt = INF;
// 用二进制枚举第一行的所有方案。
for (int i = 0; i < (1 << m); i ++ )
{
// 先将 turn 初始化为 0
memset(turn, 0, sizeof turn);
for (int j = 0; j < m; j ++ )
// 取出当前位
if (i >> j & 1) turn[1][j + 1] = 1;
// solve 返回翻转次数。
int t = solve();
// 返回-1表示不合法的方案。
if (t == -1) continue;
else if (t < cnt)
{
cnt = t; // 更新cnt
memcpy(ans, turn, sizeof turn);
}
}
// 一个方案都没有
if (cnt == INF) cout << "IMPOSSIBLE" << endl;
else // 输出步数最少的方案
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cout << ans[i][j] << (j == m ? "\n" : " ");
}
return 0;
}