题目描述
给你一个01矩阵,矩阵大小为M x N。(1 <= M , N <= 15)
每次操作选择一个格子,使得该格子与上下左右四个格子的值翻转。
至少多少次操作可以使得矩阵中所有的值变为0?
请输出翻转方案,若没有方案,输出"IMPOSSIBLE” 。
输入格式
第一行输入两个数:M和N。(1 <= M , N <= 15)
接下来M行,每行N个数,其值只为0或1。
输出格式
输出M行,每行N个数。
每个数代表该位置翻转次数
样例输入
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
样例输出
0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0
题解:
- 题目数据较小,可以考虑用DFS搜索
- 简单的模拟枚举会TLE,考虑简化一下:
- 如果从 按行往下 开始枚举,则可以让只有当前位置的上一行为 1 时才翻转当前位置
- 而对于第一行,则有 种翻转的可能,对此对于每一种可能进行搜索。最后根据最后一行是否全是0判断成功与否。
- 对于此类两面翻转问题,判断一个位置的属性可以通过(被翻转次数)% 2 得到。这样就不需要在原数据上模拟翻转了
- 在该问题中 被翻转次数 = 当前位置的属性(0/1) + 上下左右中 五个位置的翻转次数
- 模拟第一行的情况可以使用二进制枚举。
#include<cstdio>
#include<cstring>
#include<iostream>
//#define DEBUG
using namespace std;
int m, n;
int a[20][20], cnt[20][20], ans=0x3f3f3f3f, sumc = 0, anscnt[20][20];
int dir[5][2] = {{0,0},{0,1},{0,-1},{1,0},{-1,0}};
bool surpass(int i, int j){
if(i<0 || i>=m || j<0 || j>=n) return true;
return false;
}
int get(int i, int j)
{
int temp = a[i][j];
for(int k=0;k<5;k++)
{
int x = i+dir[k][0];
int y = j+dir[k][1];
if(!surpass(x, y)) temp += cnt[x][y];
}
return temp & 1;
}
void dfs()
{
for(int i=0;i<(1<<n);i++)
{
memset(cnt, 0, sizeof cnt);
sumc = 0; //改变的次数
int k = i;
for(int j=0;j<n;j++){
cnt[0][n-j-1] = k & 1;
sumc+= cnt[0][n-j-1];
k >>= 1;
}
for(int j=1;j<m;j++)
{
for(k=0;k<n;k++)
{
if(get(j-1, k)) //上方为1
{
cnt[j][k]=1;
sumc++;
}
}
}
int sumz = 0;
for(int j=0;j<n;j++)
{
sumz += get(m-1,j); // 统计最后一行的0
}
if(sumz==0 && sumc < ans)
{
ans = sumc;
memcpy(anscnt, cnt, sizeof cnt);
}
}
}
int main()
{
scanf("%d %d", &m, &n);
int sum=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++){
scanf("%d", &a[i][j]);
sum+=a[i][j];
}
}
memset(cnt, 0, sizeof cnt);
dfs();
if(ans == 0x3f3f3f3f) cout << "IMPOSSIBLE" << endl;
else
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
printf("%d%c", anscnt[i][j], (j==n-1)?'\n':' ');
}
return 0;
}