传送门:POJ3279
题意:牛翻地,每翻动一块地其上下左右的四块地也会跟着翻转,问最怎样用最少的步数把地都翻成正面向上。(1代表反面,0代表正面)并且输出要是字典序最小。
思路:典型开关问题,不过是二维的,建议先做POJ3276的一维开关问题,至于开关问题的理论推导,特别推荐看挑战程序设计P150-P157,尤其是最后的集合整数的讲解表示个人感觉极为精妙。
然后就题论题,本题主要思路就是先枚举第一行的状态,第一行的状态确定了,那么如果第一行有反面向上的,只能通过翻转第二行去改变,如此第二行的翻转情况就被确定了,以此类推直到最后一行,然后检查最后一行的状态,看看最后一行是否全部正面朝上来判断此解是否可行。
重点:1.同一个格子翻两次就会恢复原状
2.翻地的集合确定了,翻的先后顺序是无关紧要的
3.要做到输出是字典序最小,就要在枚举第一行状态的时候按照字典序枚举。
PS:此题最快解法应该是高斯消元,不过蒟蒻还没学。。等到学了以后再掏出来练手吧。
代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<vector>
#include<map>
#define ll long long
#define pi acos(-1)
#define inf 99999999
using namespace std;
typedef pair<int,int>P;
int m,n;
int mp[20][20],flip[20][20],ans[20][20];//原数组、临时数组、结果数组
int go[5][2]={0,0,1,0,0,1,-1,0,0,-1};
int getcolor(int i,int j)//判断某块地是正面还是反面
{
int sum=mp[i][j];
for(int k=0;k<5;k++)
{
int di=i+go[k][0];
int dj=j+go[k][1];
if(0<=di&&di<m&&0<=dj&&dj<n)
sum+=flip[di][dj];
}
return sum&1;
}
int check()//确定第一行的状态下判断是否有可行解
{
for(int j=1;j<m;j++)
{
for(int k=0;k<n;k++)
{
flip[j][k]=getcolor(j-1,k);
}
}
for(int i=0;i<n;i++)
if(getcolor(m-1,i))
return -1;
int sum=0;
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
sum+=flip[i][j];
return sum;
}
void solve()
{
int res=inf;
for(int i=0;i<1<<n;i++)//枚举第一行状态
{
memset(flip,0,sizeof(flip));
for(int j=0;j<n;j++)
{
flip[0][n-1-j]=(i>>j)&1;//集合的整数表示
}
int t=check();
if(t>=0&&res>t)
{
res=t;
memcpy(ans,flip,sizeof(flip));
}
}
if(res==inf)
printf("IMPOSSIBLE\n");
else
{
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
printf("%d%c",ans[i][j]," \n"[j==n-1]);
}
}
int main()
{
while(~scanf("%d%d",&m,&n))
{
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
scanf("%d",&mp[i][j]);
solve();
}
return 0;
}