昨天晚上12点刷到的这个题,一开始一位是BFS,但是一直没有思路。后来推了一下发现只需要依次枚举第一行的所有翻转状态然后再对每个情况的其它田地翻转进行暴力dfs就可以,但是由于二进制压缩学的不是很透,一直有小问题,下面我还会讲子集生成的相关方法,有兴趣的同学可以继续关注。
本题大意:一块地,有黑(1)白(0)之分,牛每次踩踏使得当前块以及四连块都变色,问当牛如何踩时使得地都变白并且求出最小踩踏次数和踩踏路径的最小字典序时的踩踏地图。
本题思路:由于同一块地被翻两次都会回到原来的状态,所以只需要对应每块地看他上方的地是否为黑色,为黑色则翻否则看其他情况,由于第一排的黑色只有第二排能翻,所以需要先对第一排进行枚举,然后再对其剩余的状态进行搜索即可。那么如何判断某块地是否为黑色呢,这里我们采用二进制压缩,大意就是从1 -(1 << n) 的所有数字即逆序枚举了所有状态,然后访问每个状态,如果此地上方的为白色地则跳过,黑色地则进行翻转。具体如下图所示。
首先我们假设有m == 4列,则它所对应的数字如下图所示。
每个数对应的二进制码位如果为1则翻转,否则不翻转。
参考代码:
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 5 const int maxn = 15 + 5, INF = 0x3f3f3f; 6 int n, m, minx = INF; 7 int maze[maxn][maxn], temp[maxn][maxn], vis[maxn][maxn], ans[maxn][maxn]; 8 int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1}; 9 10 void reverse(int u, int v) { 11 vis[u][v] = 1; 12 temp[u][v] = !temp[u][v]; 13 for(int p = 0; p < 4; p ++) { 14 int ni = u + dx[p], nj = v + dy[p]; 15 if(ni >= 0 && nj >= 0 && ni < n && nj < m) 16 temp[ni][nj] = !temp[ni][nj]; 17 } 18 } 19 20 bool Judge() { 21 for(int i = 0; i < n; i ++) 22 for(int j = 0; j < m; j ++) 23 if(temp[i][j] == 1) return false; 24 return true; 25 } 26 27 void solve(int x) { 28 memcpy(temp, maze, sizeof(maze)); 29 memset(vis, 0, sizeof(vis)); 30 int cnt = 0; 31 for(int i = 0; i < m; i ++) { 32 if((x >> i) & 1) { 33 cnt ++; 34 reverse(0, i); 35 } 36 } 37 for(int i = 1; i < n; i ++) { 38 for(int j = 0; j < m; j ++) { 39 if(temp[i - 1][j] == 1) { 40 reverse(i, j); 41 cnt ++; 42 } 43 } 44 } 45 if(Judge() && cnt < minx) { 46 minx = cnt; 47 memcpy(ans, vis, sizeof(vis)); 48 } 49 } 50 51 int main () { 52 scanf("%d %d", &n, &m); 53 for(int i = 0; i < n; i ++) 54 for(int j = 0; j < m; j++) 55 scanf("%d", &maze[i][j]); 56 for(int i = 0; i < (1 << m); i ++) 57 solve(i); 58 if(minx == INF) 59 printf("IMPOSSIBLE "); 60 else { 61 for(int i = 0; i < n; i ++) { 62 for(int j = 0; j < m - 1; j ++) { 63 printf("%d ", ans[i][j]); 64 } 65 printf("%d ", ans[i][m - 1]); 66 } 67 } 68 return 0; 69 }