POJ 3279 Fliptile[二进制状压DP]

时间:2022-11-08 04:21:16

题目链接【http://poj.org/problem?id=3279】

题意:给出一个大小为M*N(1 ≤ M ≤ 15; 1 ≤ N ≤ 15) 的图,图中每个格子代表一个灯泡,mp[i][j] = 0;表示(i,j)这盏灯灭,反之则亮。如果把反转(i,j)等的开关,那么这个灯和他上下左右的等都会变成原来相反的的状态。

问:用最少的使用开关的次数使得灯全部被熄灭。

思路:用0,1表示第一行每个位置是否使用开关,所有状态最多有(1<<15)-1种,如果第一行的状态确定了,其他行的开关状态也就确定了。即:当第一行开关反转过后,那么如果第一行(i,j)灯为0,呢么下一行(i+1,j)灯的开关就不能反转,反之必须反转,只有这样,当前行的灯才能被全灭。所以,枚举第一行的所有状态,逐层向下模拟,最后判断最后一行是否被全灭即可,同时维护最小值的状态即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int INF = 1e5;
const int maxn = << ;
int mp[][];
int t[][];
int ans[][];
int n, m;
int dir[][] = {, , , , , -, -, };
bool check()
{
for(int j = ; j <= m; j++)
if(t[n][j]) return ;
return ;
}
void init()
{
for(int i = ; i <= n; i++)
for(int j = ; j <= m; j++)
t[i][j] = mp[i][j];
}
int flip(int x, int y)
{
t[x][y] ^= ;
for(int i = ; i <= ; i++)
{
int X = x + dir[i][];
int Y = y + dir[i][];
if(X >= && X <= n && Y >= && Y <= m)
t[X][Y] ^= ;
}
}
int tap(int k)
{
init();
int num = ;
//处理第一行
for(int i = ; k; i++)
{
if(k & )
flip(, i), num++;
k >>= ;
}
//处理后面的行数
for(int i = ; i < n; i++)
for(int j = ; j <= m; j++)
{
if(t[i][j])
flip(i + , j), num++;
}
return num;
}
void tap2(int k)
{
init();
memset(ans, , sizeof(ans));
for(int i = ; k; i++)//第一行
{
if(k & )
flip(, i), ans[][i] = ;
k >>= ;
}
for(int i = ; i < n; i++)
for(int j = ; j <= m; j++)
{
if(t[i][j])
flip(i + , j), ans[i + ][j] = ;
}
}
int main ()
{
while(~scanf("%d%d", &n, &m))
{
for(int i = ; i <= n; i++)
for(int j = ; j <= m; j++)
scanf("%d", &mp[i][j]);
int mask = ( << m) - ;
int T = INF;
for(int i = ; i <= mask; i++)
{
int num = tap(i);//反转
if(check() && num < T)
{
T = num;
tap2(i);//记录反转
}
}
if(T == INF)
printf("IMPOSSIBLE\n");
else
{
for(int i = ; i <= n; i++)
{
for(int j = ; j < m; j++)
printf("%d ", ans[i][j]);
printf("%d\n", ans[i][m]);
}
}
}
return ;
}