Fliptile (二进制压缩)
Posted -ackerman
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fliptile (二进制压缩)相关的知识,希望对你有一定的参考价值。
题目链接:http://poj.org/problem?id=3279
题目大意:有一个n*m的棋盘,0表示白色,1表示黑色。每次可以翻转当前位置,它的上下左右四个位置也会被相应翻转。问最少翻转多少次会使所有棋面显示为白色,并给出需要翻转的位置,0表示不翻转,1表示翻转。
思路:第一行的翻转状态决定了你第二行的翻转状态。也可以说第n-1行的翻转状态决定了第n行应该如何去翻转。 所以我们可以说第一行起到了决定性的作用
所以我们去枚举第一行的所有的可能的情况。
这道题比较巧妙的地方就是它利用了二进制压缩,更加方便的去枚举了第一行的每种情况。
首先让我们来学习一下左移<<的概念,其实很好理解,就是将一个数转换为二进制,然后向左移动若干位,然后在多出来的位置上补零,比如,对于5<<2,5的二进制为101,左移两位就是10100,那么最后的结果就是20,。
利用这个特点,我们可以通过使用一个特殊的数字1,来解决这个枚举问题。枚举的第一步就是确定到底要改变哪几位数,联想到二进制,我们可以这样处理,就题目的测试数据而言,一行有四个数,用一个二进制数xxxx表示,易知,xxxx一共有2^4种排列,其实也是1<<4,之所以这样写,是因为这比pow要快,所以,我们让k从0开始枚举到15,也就是0000到1111,然后规定,只要是带1的位置,就要翻转这个位置的数字,问题又来了,怎么知道哪一位是1,呢,这里还是用到了二进制,即与运算,我们让k分别与1000,0100,0010,0001进行与运算,分别对应不同的位,如果结果不是1,说明这一位上不是0,是不是灰常巧妙,当然,那四个值依然是通过1的左移来计算出来的
具体代码:
1 #include <iostream> 2 #include <string> 3 #include <cstring> 4 using namespace std; 5 6 int Map[20][20],cal[20][20],out[20][20]; 7 int n,m; 8 int dir[5][2] = 0,0,0,1,0,-1,1,0,-1,0; 9 10 int fuc(int x,int y) //(x,y)的状态由本身的黑白 + 周围五个的翻转状态决定 11 int temp = Map[x][y]; 12 13 for(int i = 0;i < 5;i ++) 14 int xi = x+dir[i][0]; 15 int yi = y+dir[i][1]; 16 17 if(xi < 1 || xi > n || yi < 1 || yi > m) continue; 18 temp += cal[xi][yi]; 19 20 return temp%2; 21 22 int dfs() 23 for(int i = 2;i <= n;i ++) 24 for(int j = 1;j <= m;j ++) 25 if(fuc(i-1,j)) //如果上方为黑色,必须要翻转 26 cal[i][j] = 1; 27 28 for(int i = 1;i <= m;i ++) //最后一行全白 29 if(fuc(n,i)) 30 return -1; 31 32 int res = 0; 33 for(int i = 1;i <= n;i ++) 34 for(int j = 1;j <= m;j ++) 35 res += cal[i][j]; 36 return res; 37 38 39 int main() 40 41 while(cin>>n>>m) 42 for(int i = 1;i <= n;i ++) 43 for(int j = 1;j <= m;j ++) 44 cin>>Map[i][j]; 45 46 int flag = 0; 47 int ans = 0x3f3f3f3f; 48 for(int i = 0;i < 1<<m;i ++) //第一行 1<<m种状态,二进制从0开始,字典序从小到大 49 memset(cal,0,sizeof(cal)); 50 51 for(int j = 1;j <= m;j ++) //利用二进制枚举第一行所有的情况 52 cal[1][m-j+1] = i>>(j-1) & 1; // cal数组存贮的就是翻转的情况了 53 int cont = dfs(); 54 if(cont >= 0 && cont < ans) //翻转次数最少 55 flag = 1; 56 ans = cont; 57 memcpy(out,cal,sizeof(cal)); 58 59 60 if(!flag) cout<<"IMPOSSIBLE"<<endl; 61 else 62 for(int i = 1;i <= n;i ++) 63 for(int j = 1;j <= m;j ++) 64 if(j != 1) cout<<" "; 65 cout<<out[i][j]; 66 67 cout<<endl; 68 69 70 71 return 0; 72
以上是关于Fliptile (二进制压缩)的主要内容,如果未能解决你的问题,请参考以下文章