题目:
给定一个 M×N 的 01 矩阵。
你需要选择其中一些元素,并对选择的元素进行翻转操作。
翻转操作是指将所选元素以及与其上下左右相邻的元素(如果有)进行翻转(0 变 1,1 变 0)。
我们希望最终矩阵变为一个全 0 矩阵,并且选择进行翻转操作的元素数量尽可能少。
输出最佳翻转方案。
输入格式:
第一行包含整数 M,N。
接下来 M 行,每行包含 N 个整数,每个整数为 0 或 1。
输出格式:
共 M 行,每行包含 N 个整数,其中第 i 行第 j 列的整数,表示第 i 行第 j 列元素的翻转次数。
如果翻转操作次数最少的操作方案不唯一,则输出字典序最小的方案。
如果不存在合理方案,则输出 IMPOSSIBLE。
样例输入:
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
解题思路:
当第一行翻转完后, 想要去掉第一行的1, 需要翻转第二行对应列的位置。
依次类推, 当第i行确定时, 只有i+1行对应列的位置翻转才会影响到它。
对于相同的位置, 要么翻一次, 要么不翻, 因为翻两次会恢复原样, 翻第三次相当于只翻了一次。
数据范围在[1, 15], 第一行的情况只有 2^15 种, 暴力枚举第一行(翻转情况,0代表不翻转,1代表翻转)即可。
对于每种第一行的情况, 全部翻转完后, 遍历一次最后一行, 如果有1说明该方案不可行, 否则更新最优解。
题解代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20;
const int dx[]= {-1, 0, 1, 0};
const int dy[]= {0, 1, 0, -1};
int g[N][N],back[N][N],m,n;//g为临时地图,back保存初始状态
int res[N][N],tmp[N][N];//最优答案,临时答案
void fanzhuan(int x,int y) {
g[x][y]^=1;//0变1,1变0
for(int i=0; i<4; i++) {
int X=dx[i]+x;
int Y=dy[i]+y;
if(X<0||X>=n||Y<0||Y>=m)continue;
g[X][Y]^=1;
}
}
signed main() {
cin>>n>>m;
int ans=0x3f3f3f3f;
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
cin>>g[i][j];//原地图
back[i][j]=g[i][j];
//备份地图,每次只需要改变第一行的状态即可
}
}
for(int op=0; op<(1<<m); op++) {
//第一行的所有情况
memcpy(g,back,sizeof g);//恢复现场
memset(tmp,0,sizeof tmp);
int cnt=0;
for(int j=0; j<m; j++) {
if(op>>j&1) {//是1代表这里翻转,得到一个状态
fanzhuan(0,m-j-1);
cnt++;
tmp[0][m-j-1] = 1;
}
}
for(int j = 1; j < n; j++) { //翻转第二行到最后一行
for(int k = 0; k < m; k++)
if(g[j - 1][k]) fanzhuan(j, k), cnt++, tmp[j][k] = 1;
}
bool f=false;
for(int j = 0; j < m; j++) //遍历最后一行
if(g[n - 1][j]) {
f = true;
break;
}
if(f)continue;
if(ans>cnt) {
ans=cnt;
memcpy(res, tmp, sizeof res);
}
}
if(ans == 0x3f3f3f3f) cout << "IMPOSSIBLE";
else {
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++)
cout << res[i][j] << ' ';
cout << '\n';
}
}
return 0;
}