【简单搜索】POJ 3279 Flip tile 详解

Fliptile:

题目链接:https://vjudge.net/problem/POJ-3279

Farmer John knows that an intellectually satisfied cow is a happy cow who will give more milk. He has arranged a brainy activity for cows in which they manipulate an M × N grid (1 ≤ M ≤ 15; 1 ≤ N ≤ 15) of square tiles, each of which is colored black on one side and white on the other side.

As one would guess, when a single white tile is flipped, it changes to black; when a single black tile is flipped, it changes to white. The cows are rewarded when they flip the tiles so that each tile has the white side face up. However, the cows have rather large hooves and when they try to flip a certain tile, they also flip all the adjacent tiles (tiles that share a full edge with the flipped tile). Since the flips are tiring, the cows want to minimize the number of flips they have to make.

Help the cows determine the minimum number of flips required, and the locations to flip to achieve that minimum. If there are multiple ways to achieve the task with the minimum amount of flips, return the one with the least lexicographical ordering in the output when considered as a string. If the task is impossible, print one line with the word "IMPOSSIBLE".

Input:

Line 1: Two space-separated integers: M and N
Lines 2..M+1: Line i+1 describes the colors (left to right) of row i of the grid with N space-separated integers which are 1 for black and 0 for white

Output:

Lines 1..M: Each line contains N space-separated integers, each specifying how many times to flip that particular location.

 题面翻译:

 给你一个01矩阵,你可以选择翻转某一个点,使其由0变为1或反之。但是每翻转一个点会导致其上下左右四个点一起翻转,现问你最少的翻转次数如何达成,若有多种方式,输出字典序最小的一种。

 这题是一道二进制枚举题,被归类在了简单搜索里。但仔细一想,枚举不就是最暴力的搜索方式吗?

 分析:

  1)首先,一个点翻转两次及两次以上是没有意义的。因为翻转偶数次等价于不翻转,翻转奇数次等价于翻转一次,那么既然我们现在要求翻转次数最少的情况,就不需要考虑翻转两次及以上的情况了。

  2)每个点翻转1次或0次,我第一时间想到的是二进制枚举。但是这道题点的个数最多为225(15*15)个,直接枚举是不可能的。因为二进制枚举的时间复杂度是n*(2^n)次,20个点的情况都没法枚举;

  3)但是这道题存在一个规律,那就是第一行的翻转情况确定后,后面几行的翻转情况是可以递推出来的。

   以题目样例为例:

  1 0 0 1
  0 1 1 0
  0 1 1 0
  1 0 0 1

    假设第一行的翻转情况是0 0 0 0 即都不翻转

    那么当前矩阵第一行情况是1 0 0 1;

    对于(1,1)点上的1,因为它右边的点已经确定不翻转了,左边和上面没有点,那么如果它想变为0 只能由(2,1)点翻转一次得到,(1,4)点上的1同理。

    推而广之,因为我们是从第一行往下推,假设我们推到了第N行的第M个元素,那么如果第N-1行的第M个元素为1,它左边右边上边翻转情况已经确定,要想变为0只能由当前行(即第N行)的点翻转来实现。

    4)如果推到最后一行,还有1存在,说明这种(第一行的)情况本身就是不可行的

    5)二进制枚举第一行还有一个好处就是它本身就是按照字典序大小枚举的,完美符合题目要求

 顺便吐槽一下这题因为我把"IMPOSSIBLE"打成了"Impossible"  WA了好几发(心痛)

 代码详解:   

  tile数组存放题目给的01矩阵

  我们要用二进制枚举第一行所有的情况,然后每个情况都求解其所用的翻转次数。

  turn数组存放每次求解时的翻转情况

  ans数组存放答案,如果有比当前最少翻转次数还少的就更新答案。

  direct存放翻动的四个方向

int N,M;
int tile[20][20];
int turn[20][20];  //翻转情况
int ans[20][20];
int direct[4][2]={{0,1},{0,-1},{1,0},{-1,0}};

  opposite函数描述0和1的翻转。

int opposite(int x){
  if(x==0)
     return 1;
  return 0;
}

  核心部分:

int solve(){   //int类型返回当前情况解的翻转次数,若当前情况无解返回-1
    int flag=0;     //flag记录是否有解


    int New[20][20];              //新建一个数组
    for(int i=1;i<=N;i++)
       for(int j=1;j<=M;j++){
           New[i][j]=tile[i][j];  
    }



   for(int i=1;i<=N;i++){
      for(int j=1;j<=M;j++){
              if(i!=1&&New[i-1][j]==1)  //第一行的翻转情况是确定的;
                 turn[i][j]=1;          //其余行的翻转情况由上一行是否为1确定

          if(turn[i][j]==1){            //1表示翻转,自身和四周四个点都要翻转
                 New[i][j]=opposite(New[i][j]);
                 for(int k=0;k<4;k++){
                     int tempx=i+direct[k][0];
                     int tempy=j+direct[k][1];
                     New[tempx][tempy]=opposite(New[tempx][tempy]);
                 }
          }

      }
   }

   int ret=0;                    //记录翻转所用的次数
   for(int i=1;i<=N;i++)
     for(int j=1;j<=M;j++){
       ret+=turn[i][j];
   }

   for(int i=1;i<=M;i++)
      if(New[N][i]!=0){         //如果最后一行不全为0说明当前情况无解
         flag=1;
         break;
   }

   if(!flag)
       return ret;
   return -1;
}

 下附完整AC代码:

#include <cstdio>
#include <queue>
#include <string.h>
#include <iostream>
using namespace std;

int N,M;
int tile[20][20];
int turn[20][20];  //翻转情况
int ans[20][20];
int direct[4][2]={{0,1},{0,-1},{1,0},{-1,0}};

int opposite(int x){
  if(x==0)
     return 1;
  return 0;
}

int solve(){
    int flag=0;


    int New[20][20];
    for(int i=1;i<=N;i++)
       for(int j=1;j<=M;j++){
           New[i][j]=tile[i][j];
    }



   for(int i=1;i<=N;i++){
      for(int j=1;j<=M;j++){
              if(i!=1&&New[i-1][j]==1)
                turn[i][j]=1;

          if(turn[i][j]==1){
                 New[i][j]=opposite(New[i][j]);
                 for(int k=0;k<4;k++){
                     int tempx=i+direct[k][0];
                     int tempy=j+direct[k][1];
                     New[tempx][tempy]=opposite(New[tempx][tempy]);
                 }
          }

      }
   }

   int ret=0;
   for(int i=1;i<=N;i++)
     for(int j=1;j<=M;j++){
       ret+=turn[i][j];
   }

   for(int i=1;i<=M;i++)
      if(New[N][i]!=0){
         flag=1;
         break;
   }

   if(!flag)
       return ret;
   return -1;
}

void copy_toans(){
  for(int i=1;i<=N;i++)
    for(int j=1;j<=M;j++)
        ans[i][j]=turn[i][j];
}

int main()
{
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++)
             scanf("%d",&tile[i][j]);


    int t=0;
    int ans_min=0xfffffff;
    for(int i=0;i<(1<<M);i++){    //二进制枚举
         memset(turn,0,sizeof(turn));
         for(int j=0;j<M;j++)
            if(i&(1<<j)){
                turn[1][j+1]=1;
            }

         int temp=solve();
         if(temp!=-1){
                t=1;
                if(temp<ans_min){
                    copy_toans();
                    ans_min=temp;
                }
         }
    }
    if(t){
      for(int i=1;i<=N;i++){
            for(int j=1;j<=M;j++)
               printf("%d ",ans[i][j]);
      printf("\n");
      }
    }
    else
        printf("IMPOSSIBLE\n");
    return 0;
}


 

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值