http://acm.hdu.edu.cn/showproblem.php?pid=1882
感觉非常不错的一道题。
给一个n*m(1<=n,m<=16)的矩阵,每一个格子都有黑白两面,当翻一个格子时,它的上下左右都要翻转,问最后使格子全变为白色的最少翻转步数。
仅仅需枚举第一行的状态即可,由于对于第i(i>=2)行j列翻转情况受上一行的制约,仅仅有当上一行也是‘X’的时候,该行j列才干翻转,使i-1行j列变为‘.’,否则i行j列不能翻转。依次进行下去,当最后一行全变为白色,说明翻转成功。
一个非常重要的优化:当n < m时,将矩阵转置,这样状态数由 (1<<m)-1 变为 (1<<n)-1。
跑了280ms,看了其它人的博客,他应该是按行翻转的。只是那一些个位运算,搞不懂。。。
#include <stdio.h> #include <string.h> #include <algorithm> #include <stack> #include <vector> #include <queue> #define LL long long #define _LL __int64 using namespace std; const int INF = 0x3f3f3f3f; int n,m; char map[17][17]; int sta[17],tmp[17]; int bit[17]; int ans; void cal() { bit[0] = 1; for(int i = 1; i < 17; i++) bit[i] = (bit[i-1] << 1); } void input() { memset(sta,0,sizeof(sta)); //记录每一行的状态 if(n >= m) { for(int i = 0; i < n; i++) { scanf("%s",map[i]); for(int j = 0; j < m; j++) { if(map[i][j] == 'X') sta[i] = (sta[i] << 1) + 1; else sta[i] <<= 1; } } } //优化,当m < n时矩阵转换 else { for(int i = 0; i < n; i++) { scanf("%s",map[i]); for(int j = 0; j < m; j++) { if(map[i][j] == 'X') sta[j] = (sta[j] << 1) + 1; else sta[j] <<= 1; } } swap(n,m); } } void solve() { int step; ans = INF; for(int i = 0; i < (1<<m); i++) { memcpy(tmp,sta,sizeof(sta)); step = 0; //先找出第一行应该翻转的列并进行翻转 for(int j = 0; j < m && step < ans; j++) { if(bit[j]&i) { step++; if(j > 0) tmp[0] ^= bit[j-1]; if(j < m-1) tmp[0] ^= bit[j+1]; tmp[0] ^= bit[j]; tmp[1] ^= bit[j]; } } //依据j-1行的状态依次翻转第j行 for(int j = 1; j < n && step < ans; j++) { for(int k = 0; k < m && step < ans; k++) { if(bit[k]&tmp[j-1]) { step++; if(k > 0) tmp[j] ^= bit[k-1]; if(k < m-1) tmp[j] ^= bit[k+1]; tmp[j] ^= bit[k]; tmp[j+1] ^= bit[k]; } } } if(!tmp[n-1]) ans = min(ans,step); } } int main() { cal(); while(~scanf("%d %d",&n,&m)) { if(n == 0 && m == 0) break; input(); solve(); if(ans == INF) printf("Damaged billboard. "); else printf("You have to tap %d tiles. ",ans); } return 0; }