https://vjudge.net/problem/POJ-3279
参考博客: https://blog.csdn.net/loy_184548/article/details/50949972
这道题目在vj上的kuangbin比赛中被归类到了搜索, 其实它还算不上搜素, 是一道比较基础的枚举题目,和熄灯问题, 画家问题,拨钟问题是一类题, 如果单纯用爆搜或者枚举来考虑的话可能会超时,复杂度O(2^N*M) 需要一点点小的技巧
首先, 我们来考虑一下, 改变当前一行的只有按下当前一行或者是上一行正对着的按钮.
所以, 我们只用考虑第一行, 枚举第一行的开关操作, 然后与之对应的第二行就确定了, 第二行又确定了第三行...以此类推, 直到最后一行. 我们来判断是否全部归0
需要注意的是题中要求输出最优解, 多个最优解又要求以字典序最小的输出, 我在此用了一个字典序的状态压缩, 可以直接拿走下次用.
//Flip Tile 枚举
#include <stdio.h>
#include<cstring>
const int maxn = 20;
int M, N;
int color[maxn][maxn], cur[maxn][maxn];//题中读入颜色, 当前操作颜色(防止直接改变源颜色)
int oper[maxn][maxn], ans[maxn][maxn], steps = 0, minSteps = 1<<30; //当前操作, 最小操作, 当前解和最小解
void press(int x, int y)
{ //按下x, y处的按钮
cur[x][y]^=1, cur[x+1][y]^=1, cur[x-1][y]^=1, cur[x][y+1]^=1, cur[x][y-1]^=1;
}
bool solve()
{ //判断是否已经解决问题
memcpy(cur, color, sizeof(color));
//根据枚举结果改变第一二行
for(int i = 1; i <= N; i++)
if(oper[1][i])
press(1, i), steps++;
//根据第i-1行决定第i行的操作
for(int i = 2; i <= M; i++){
for(int j = 1; j <= N; j++)
if(cur[i-1][j])
oper[i][j]=1, press(i, j), steps++;
}
//判断最后一行是否满足条件
for(int i = 1; i <= N; i++)
if(cur[M][i]) return 0;
return 1;
}
int main()
{
scanf("%d%d",&M,&N);
for(int i = 1; i <= M; i++)
for(int j = 1; j <= N; j++)
scanf("%d",&color[i][j]);
//仅仅枚举第一行的状态即可
for(int i = 0; i < (1<<N); i++){ //状态压缩
memset(oper, 0, sizeof(oper)), steps = 0; //初始化不要忘
for(int j = 0; j < N; j++){
oper[1][N-j] = (i>>j&1);
}
if(solve() && steps>0 && steps<minSteps)
minSteps = steps, memcpy(ans, oper, sizeof(oper));;
}
if(minSteps < (1<<30))
for(int i = 1; i <= M; i++){
for(int j = 1; j <= N; j++)
printf("%d ",ans[i][j]);
printf("\n");
}
else printf("IMPOSSIBLE\n");
return 0;
}