题目链接:http://poj.org/problem?id=3279
题意:有一个n*m的格子,每个格子要么为0,要么为1。我们需要把所有的格子都反转成0,每反转一个格子,它上下左右的格子都会跟着反转。求最少的翻转次数,并输出对应的翻转情况。有多个解时,输出字典序最小的一组。
题目分析:首先我们发现,N,M<=15,数据范围很小,并且翻转不分先后,对一个格子翻转两次,那么相当于没有反转。
假如我们确定了第i行的状态,那么当第i行的状态确定时,第i+1行的状态也就随之确定了,为什么呢?
对于第i行翻转之后的状态,如果第i行的某个位置[i,j]还存在着一个1,那么这个1必须由翻转[i+1,j]来消除,其他的格子无法再影响[i,j]这个位置了。 而当第i+1行确定了以后,i+2行同理也确定了。所以,我们完全可以通过第i行的翻转情况推出第i+1行的翻转情况。
因为数据范围很小,我们可以直接暴力枚举第一行的所有状态,对于每个状态进行判断,记录翻转次数,不断更新答案即可。
代码解释:
数组a为原始状态
数组b为用来翻转的(每次翻转前初始b=a)
数组c为记录翻转状态的 (并且c的第一行=数组f,即暴力枚举出的所有状态)
数组d为答案数组,当翻转次数可以更新时,d也随之更新。
void dfs(int x); 枚举出第一行的所有状态,存在f[i]中
int check(); 判断当第一行为此种状态时是否有解,返回值为翻转次数
void fan(int i,int j); 翻转b[i][j]
#include<iostream>
using namespace std;
int m,n,ans=1000;
bool bi=false; //bi判断是否存在解
int a[20][20],b[20][20],c[20][20],d[20][20];
int f[20];
void dfs(int x);
int check();
void fan(int i,int j);
int main(){
cin>>m>>n;
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++) cin>>a[i][j];
dfs(1);
if (bi==true){
for (int i=1; i<=m; i++){
for (int j=1; j<=n; j++) cout<<d[i][j]<<" ";
cout<<endl;
}
} else
cout<<"IMPOSSIBLE"<<endl;
}
void dfs(int x){
if (x==n+1){
//枚举出一种第一行的翻转状态则判断一次。
int p=check();
if (p!=-1)
{
bi=true;
//更新答案
if (p<ans){
ans=p;
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++)
d[i][j]=c[i][j];
}
}
} else
for (int i=0; i<=1; i++){
f[x]=i;
dfs(x+1);
}
}
int check(){
int sum=0;
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++) {
b[i][j]=a[i][j];
c[i][j]=0;
}
//c数组每次清0,b初始化为a
for (int i=1; i<=n; i++) {
c[1][i]=f[i];
if (c[1][i]==1) {
fan(1,i);
sum++;
}
}
//把枚举出来的第一行搬过来
for (int i=2; i<=m; i++)
for (int j=1; j<=n; j++){
if (b[i-1][j]==1) {
c[i][j]=1;
fan(i,j);
sum++;
}
}
//b[i-1][j]=1 则需要翻转b[i][j]
for (int i=1; i<=n; i++)
if (b[m][i]==1) return -1;
//判断最后一行是否全是0,若全是,则此为一个解,否则返回-1
return sum;
}
void fan(int i,int j){
b[i][j]=(b[i][j]+1)%2;
b[i][j-1]=(b[i][j-1]+1)%2;
b[i][j+1]=(b[i][j+1]+1)%2;
b[i-1][j]=(b[i-1][j]+1)%2;
b[i+1][j]=(b[i+1][j]+1)%2;
}