Fliptile
题目链接:POJ - 3279题意:M*N的格子, 每个小格子边长为单位长度1, 0表示格子为白色, 1表示黑色, 每次翻转一个格子可以使它本身和四周的四个格子由1变0, 或由0变1;最后要使全部格子都是白色, 即都为0;
每个格子最多翻转一次, 若翻转两次, 便又回到了之前的状态, 所以应该有2^(n*m)种操作方式;m和n的范围是1~15, 所以如果直接爆搜的话,,,2^225, 会跑到下一次宇宙大爆炸???
换个思想, 对于第一行, 有2^n种操作, 处理完第一行后, 看下一行, 如果此行的上一行的对应位置是1, 那么一定要把此位置翻转, 因为此时上一行已经处理完,只有该位置翻转才能改变上一行的对应位置, 依次处理, 直到处理完最后一行结束;
在判断最后一行是否已经全为0(此时上边的每一行都已处理完, 且不会有1), 若是, 那么这是一种可行操作, 记录下来, 最后输出操作次数最小的;
#include <cstdio> #include <cstring> using namespace std; const int INF = 0x3f3f3f3f; int G[20][20], ans, m, n, f[20][20], p[20][20], q[20][20]; void flip(int i, int j){//翻转i,j位置,本身和四周方格同时翻转; f[i][j]=!f[i][j]; if(i+1<=m) f[i+1][j]=!f[i+1][j]; if(i-1>0) f[i-1][j]=!f[i-1][j]; if(j+1<=n) f[i][j+1]=!f[i][j+1]; if(j-1>0) f[i][j-1]=!f[i][j-1]; } int Count(int k){//第一行操作状态为k时, 已经操作的次数; int cnt=0; while(k){ cnt+=k&1; k>>=1; } return cnt; } int main(){ scanf("%d%d", &m, &n); for(int i=1; i<=m; i++){ for(int j=1; j<=n; j++){ scanf("%d", &G[i][j]); } } int res=0, w=(1<<n); ans=INF; for(int k=0; k<w; k++){ res=Count(k); memset(q, 0, sizeof(q)); for(int i=1; i<=m; i++){ for(int j=1; j<=n; j++){ f[i][j]=G[i][j]; } } for(int j=1; j<=n; j++){ if((1<<(j-1))&k){//对应位置是否翻转; flip(1, j); q[1][j]=1; } } for(int i=2; i<=m; i++){ for(int j=1; j<=n; j++){ if(f[i-1][j]){ flip(i, j); q[i][j]=1; res++; } } } int flag=0; for(int j=1; j<=n; j++){ if(f[m][j]){ flag=1; break; } } if(flag) continue; if(res<ans){ ans=res; for(int i=1; i<=m; i++){ for(int j=1; j<=n; j++){ p[i][j]=q[i][j]; } } } } if(ans>=INF) printf("IMPOSSIBLE\n"); else for(int i=1; i<=m; i++){ for(int j=1; j<=n; j++){ printf("%d%c", p[i][j], j==n?'\n':' '); } } return 0; }