题目链接【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 = 1 << 16; int mp[17][17]; int t[17][17]; int ans[17][17]; int n, m; int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0}; bool check() { for(int j = 1; j <= m; j++) if(t[n][j]) return 0; return 1; } void init() { for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) t[i][j] = mp[i][j]; } int flip(int x, int y) { t[x][y] ^= 1; for(int i = 0; i <= 3; i++) { int X = x + dir[i][0]; int Y = y + dir[i][1]; if(X >= 1 && X <= n && Y >= 1 && Y <= m) t[X][Y] ^= 1; } } int tap(int k) { init(); int num = 0; //处理第一行 for(int i = 1; k; i++) { if(k & 1) flip(1, i), num++; k >>= 1; } //处理后面的行数 for(int i = 1; i < n; i++) for(int j = 1; j <= m; j++) { if(t[i][j]) flip(i + 1, j), num++; } return num; } void tap2(int k) { init(); memset(ans, 0, sizeof(ans)); for(int i = 1; k; i++)//第一行 { if(k & 1) flip(1, i), ans[1][i] = 1; k >>= 1; } for(int i = 1; i < n; i++) for(int j = 1; j <= m; j++) { if(t[i][j]) flip(i + 1, j), ans[i + 1][j] = 1; } } int main () { while(~scanf("%d%d", &n, &m)) { for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &mp[i][j]); int mask = (1 << m) - 1; int T = INF; for(int i = 0; i <= mask; i++) { int num = tap(i);//反转 if(check() && num < T) { T = num; tap2(i);//记录反转 } } if(T == INF) printf("IMPOSSIBLE "); else { for(int i = 1; i <= n; i++) { for(int j = 1; j < m; j++) printf("%d ", ans[i][j]); printf("%d ", ans[i][m]); } } } return 0; }