• 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 }
  • 相关阅读:
    .NET Core: 在.NET Core中进行单元测试
    .NET: 使用.NET Core CLI开发应用程序
    .NET: 谈谈C#中的扩展方法
    WPF: WPF 中的 Triggers 和 VisualStateManager
    WPF: 只读依赖属性的介绍与实践
    XAML: 自定义控件中事件处理的最佳实践
    .NET: 谈谈共享项目 (Shared Project) 的使用
    UWP: 实现 UWP 应用自启动
    UWP: 通过命令行启动 UWP 应用
    在 .NET中,一种更方便操作配置项的方法
  • 原文地址:https://www.cnblogs.com/-Ackerman/p/11186485.html
Copyright © 2020-2023  润新知