题目大意:
就是给定一个m*n的矩阵代表地板砖,0代表白砖,1代表黑砖,每个砖都有两面,一面白一面黑,翻转一次即可把白变成黑把黑变成白,但与此同时,与该砖块相邻的上下左右四块转也随之翻转。要求使用最少的翻转次数,将砖块翻转。 要求输出反转位置。
思路:
这是典型的开关问题,重点是要想到第一行的格子能被第一行翻转影响,也能被第二行反转影响,所以第一行要特殊处理,二进制枚举第一行所有翻转情况,之后每一行的反转就都有唯一解了。因为剩下的过程就是:用第二行翻转来保证第一行全白,用第三行翻转来保证第二行全白......依次类推,最后检查最后一行是否都是白色,如果是就得到一个结果,否则就无解。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string.h>
#include <climits>
using namespace std;
const int MAXN = 17;
//五个方向
const int dirx[5] = {0, 1, -1, 0, 0};
const int diry[5] = {0, 0, 0, 1, -1};
int m, n; //行数,列数
int board[MAXN][MAXN]; //初始情况
int tra[MAXN][MAXN]; //临时的翻转记录
int ans[MAXN][MAXN]; //最终结果(最终的翻转记录)
//查询(x, y)的颜色
int get(int x, int y){
int c = board[x][y]; //初始颜色
for(int i=0; i<5; i++){
int xi = x + dirx[i];
int yi = y + diry[i];
if(xi>=0 && xi<m && yi>=0 && yi<n){
//加上所有翻转情况
c += tra[xi][yi];
}
}
return c % 2; //返回最后得到的颜色
}
//求第1行确定的情况下的最少操作次数
//解不存在的话返回-1
int dfs(){
//首先确定从第二行开始的翻转方法
for(int i=1; i<m; i++){ //遍历行
for(int j=0; j<n; j++){ //遍历列
if(get(i-1, j) == 1){ //检查上一行是否为黑色,判断是否需要翻转
//如果是1,需要翻转
tra[i][j] = 1;
}
}
}
//然后检查最后一行是否都为白色,判断这种方法是否可行
for(int j=0; j<n; j++){
if(get(m-1, j) == 1) return -1; //只要有一个是黑色,就不可行
}
//能到达这里,说明该方法可行,那就统计翻转次数并返回
int count = 0;
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
count += tra[i][j];
}
}
return count;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
//输入
cin >> m >> n;
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
cin >> board[i][j];
}
}
int res = -1;
for(int i=0; i<(1<<m); i++){ //遍历第一行所有翻转情况
memset(tra, 0, sizeof(tra));
for(int j=0; j<n; j++){
tra[0][j] = (i>>j) & 1; //记录第一行的翻转结果
}
int cur = dfs(); //计算当前情况的翻转次数
if(cur >= 0 && (res == -1 || cur < res)){
//遇到更优解,就重新记录
res = cur;
memcpy(ans, tra, sizeof(tra)); //把tra复制到ans中去
}
}
if(res == -1) {cout << "IMPOSSIBLE" << endl; return 0;}
else {
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
printf("%d%c", ans[i][j], j+1 == n ? '\n' : ' ');
}
}
}
}