题目链接:Fliptile
题目大意
给你了一个0,1组成的M*N的矩阵,MN的范围在0-15,要求你用最少的步数翻转一个方格,得到最后全部为零的矩阵。每次翻转一个点与它有公共边的方格也会翻转,那么就是说,当你翻转一个点的时候,他的上下左右四个方格也会翻转。以字典序列输出。
思路
题目一看到,感觉是一种开关问题,一般都会用到取反^。然后就卡顿了,,一般就是暴力出奇迹,这到题因为会牵扯到周边方格的状态,要选择一个不会影响之前格子的搜索方法。于是就可以想到,比如说,第i行j列方格状态为1需要你去翻转,选择i+1行j列的方格翻转不会则影响到第i行其他方格的状态。那么翻转方式就确定了。剩下的就是枚举方式了,从第一行的翻转情况进行枚举,第一行总共有2^N种翻转方式,由每一种翻转方式得到其后第二行的翻转方式,最后再便利最后一行验证是否翻转成功。对于每一种成功的方式比较得到最小的步数。
可以看到,最大为2^15种,因为要不停的记录每种方式下方格的状态,状态复制量较大,考虑状态压缩。
AC代码
#include <iostream>
#include <string.h>
#include <cstdlib>
using namespace std;
int M, N, ret;
int map[20][20];
int copy_[20][20];
int path[20][20];
int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};
bool try_(int way);
void change(int i, int j){
ret++; //当前方式的步数
path[i][j] += 1;
copy_[i][j] ^= 1; //按下点取反
for(int d = 0; d < 4; d++){
int ii = i + dir[d][0];
int jj = j + dir[d][1];
if(ii >= 0 && jj >= 0 && ii < M && jj < N){
copy_[ii][jj] ^= 1;
}
}
}
bool try_(int way){
memcpy(copy_, map, sizeof(map)); //初始化复制的map
ret = 0; //初始化步数,不要总是WA在初始化上
for(int j = 0; j < N; j++){ //当前第一行的翻转方式为way变成二进制的值,按位与将有1的翻转
if(way & (1 << (N-j-1))){
change(0, j);
}
}
for(int i = 1; i < M; i++){ //由way得到的翻转方式,进行处理,处理第一行要从第二处理
for(int j = 0; j < N; j++){ //就不会影响到上一行的翻转了
if(copy_[i-1][j] == 1){ //如果上一行的某列为1, 那么处理此行的此列
change(i, j);
}
}
}
for(int j = 0; j < N; j++){ //由于对一直对上一行判断处理会剩下最后一行无法处理
if(copy_[M-1][j]){ //判断最后一行是否有1,如果有则说明翻转第一行的way方法不能得到
return false; //全部翻转为零的情况
}
}
return true;
}
int main(){
ios::sync_with_stdio(false);
freopen("dtest.txt", "r", stdin);
cin >> M >> N;
for(int i = 0; i < M; i++){
for(int j = 0; j < N; j++){
cin >> map[i][j];
}
}
int min = N*M + 1; //min为最小步数的判断
int step = -1; //最小步数时的起点翻转
for(int i = 0; i < (1 << N); i++){ //对于第一行来说,要翻转总共有2^N种,0~2^N-1.枚举所有的方式
if(try_(i) && ret < min){ //如果可以全部为0并且是目前的最小步数
min = ret;
step = i;
}
}
memset(path, 0, sizeof(path)); //之前会给path赋值
if(step >= 0){
try_(step); //重新来得到路径
for(int i = 0; i < M; i++){
for(int j = 0; j < N; j++){
cout << path[i][j] << " ";
}
cout << endl;
}
}
else{
cout << "IMPOSSIBLE" << endl;
}
}