这是一个带状压的暴力Orz
对于每个点,有效的反转次数不会超过2,所以只要求出每个点的反转次数,加上其本身的0/1,再对2取模,就是这个点现在的颜色
问题来了,如何求某个点的反转次数呢?可以逆向思维,先把每个点的反转次数存在数组fli中,某个点的反转次数等于其本身反转次数以及上下左右四个点的反转次数之和(能对这个点有影响的只能是其上下左右的点),再加上其本身的颜色编号(0/1)
把全部区域变白?似乎想要消掉一个黑色点最好不要点击它本身,因为这样会污染上方以及同一行的点,需要对已经全白的区域无影响地消除黑点,最好的方式是从这个黑点的正下方点击一次
有了这个策略,就可以开始暴力了,用一个数的二进制形式表示第一行的反转次数(0/1),一共有
2m
2
m
种情况。再由每种第一行往下推,直到推到最后一行时,第一行至倒数第二行一定全白了,只要再判断一次最后一行是否全白,即可判断是否为可行解,进而去更新最优解
再然后就是,由于是用二进制从小到大枚举的,所以求解过程就是按照字典序来的
这里说说按位与。。。
1的int32位二进制是这样子的:0000000 0000000 0000000 0000001
所以不断右移按位与的时候就是检查一个数的二进制末尾是否为1。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
#define debug(x) cerr<<#x<<"="<<x<<endl;
const int maxn = 17;
int n,m,fli[maxn][maxn],map[maxn][maxn],ans=0x7fffffff,tem,st,result[maxn][maxn];
int dx[] = {0,1,0,-1,0};
int dy[] = {1,0,-1,0,0};
bool flg = false;
string ans1, aaa;
inline bool getc(int x, int y) {
int jud = map[x][y];
for(int i=0; i<=4; i++) {
int nx = x+dx[i], ny = y+dy[i];
if(1<=nx&&nx<=n&&1<=ny&&ny<=m) {
jud += fli[nx][ny];
}
}
return jud%2;
}
inline bool determine() {
for(int i=1; i<=m; i++)
if(getc(n,i)) return false;
return true;
}
int main() {
cin >> n >> m;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin >> map[i][j];
for(int i=0; i < (1<<m); i++) {
tem = 0;
memset(fli,0,sizeof(fli));
for(int j=0; j<m; j++) {
fli[1][m-j] = (i >> j) & 1;
if(fli[1][m-j]) tem++;
}
int v=0;
for(int o=1; o<=m; o++)
v+=fli[1][o];
for(int k=2; k<=n; k++) {
for(int h=1; h<=m; h++) {
if(getc(k-1,h)) { //如果上一行的这一列是黑的,要给他翻白吧
fli[k][h] = 1;
tem++;
}
}
}
if(determine()) {
flg = true;
if(tem < ans){
ans = tem;
for(int k=1; k<=n; k++)
for(int h=1; h<=m; h++)
result[k][h] = fli[k][h];
}
}
}
if(!flg) {
cout <<"IMPOSSIBLE"<<endl;
return 0;
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++)
cout << result[i][j] << " ";
cout << endl;
}
return 0;
}