POJ1753

POJ 1753 Flip Game

作者: 麋鹿
日期:2016/8/18
方法:递归+枚举

题目描述

题目链接为 Flip Game
题目要求:
有4*4的正方形,每个格子要么是黑色,要么是白色,当把一个格子的颜色改变(黑->白或者白->黑)时,其周围上下左右(如果存在的话)的格子的颜色也被反转,问至少反转几个格子可以使4*4的正方形变为纯白或者纯黑?

解题思路

首先考虑数据结构:

棋盘用一维数组int bits[15]来存储,bit[i]=b,表示是黑色棋子,否则表示是黑色的棋子;
当思考到这里时候,发现通过题意可以明确得出的数据结构就没有了,其他的在设计算法的时候考虑。

接着考虑算法:

当0个棋子被翻转的时候,检查此时棋盘状态,即是否有纯色出现,若有,则输出0,结束;否则,考虑选择翻转1个棋子,重复上述的操作……若翻转16个棋子之后还没有出现纯色的棋盘,则输出impossible,结束。

在明确了解题算法后,我们可以发现:

① 检查棋盘是否有纯色出现–>对应一个函数,该函数返回0/1两种结果,0表示无纯色出现,1表示有纯色出现。可以得出函数的头格式大致为: int all_white_or_black(int *bits,int len)

在分析的过程中,我们也抽象出了②中的函数。

②题目要求的一次翻转引起的改变–>对应一个函数,该函数需要的操作是:将该棋子四个方向(如果有的话)上的棋子也同样翻转,无需返回结果。因为棋子是以一维数组的形式存储的,因此参数中需要有一个int i,i表示该棋子在一维数组中的下标。可以得出该函数的头格式大致为:void change_color(int *arr,int i)
那么如何做一维数组到二维棋盘的映射呢?
首先对于二维棋盘来说,不是所有的棋子都有4个方向的。
棋盘示意图
只有图中红色方框内的才有4个方向,其他位置的都会有缺少。
为了将一维数组下标i 映射到二维数组(x,y),我们可以得出i和x,y的关系:
x=i/4;
y=i%4;
当x=0时,(即第一行)统一无上方向的棋子,故当x>0时,均可以修改上方向的棋子,即 arr[i-4]=!arr[i-4];
当x=3时,(即第四行)统一无下方向的棋子,故当x<3时,均可修改下方向的棋子,即arr[i+4]=!arr[i+4];
当y=0时,(即第一列)统一无左方向的棋子,故当y>0时,修改左方向的棋子arr[i-1]=!arr[i-1];
当y=3时,(即第四列)统一无右反向的棋子,故当y<3时,修改右方向的棋子arr[i+1]=!arr[i+1];
注意:我们都是拿一维数组下标i来建立关系的,因此要知道i+1在二维数组中表现为右方,i-1表现为左方,i+4表现为下方,i-4表现为上方。
至此,该函数思路滤清楚。

下面就是关键的递归部分。

③首先检查不翻转情况下,棋盘是否出现纯色,
all_white_or_black(bits,16)
若函数返回1,输出0,结束
否则要进入开始翻转的部分。
此时main函数中结构大致为

int main{
 int bits[16];
 /*输出处理*/
 if(all_white_or_black(bits,16))
 printf("%d\n",0);
 else{
    /*翻转部分*/
 }
 return 0;
}

下面研究翻转部分,这也是这个问题的重点部分。
int j(j表示翻转棋子的个数可以从1–16变化) –>得出一个for循环
在for循环中,一次处理 翻转j次的操作。在这j次的操作中,返回结果可能为成功找到纯色棋盘,也可能没有找到,如何标注找到/没找到?
我们先在这儿标注一下。因为这涉及到j次操作中具体的处理,我们先讨论具体到j次翻转应该如何做。

假设j=3. 也就是说我现在要从16个棋子中选取3个棋子并对其翻转。
那么这3个棋子的下标我们需要保存起来,存储格式为 一维数组 int result[j]。
然后依次根据result数组中保存的j个下标,来进行对应的翻转。

forj=0;j<result的长度(NUM);j++){
change_color(new_arr,result[j]);
}

这个时候,我们要考虑的就是如何确定出这j个棋子具体的下标呢?我们可以这么考虑,认为第1个棋子为从16个棋子中选取1个,第2个棋子从余下的15个棋子中选取,第j个棋子从余下的17-j个棋子中选取。
这是一个递归的过程。

combine(int *arr,int len,int *result,int count,int NUM)
//其中arr表示棋子存储的一维数组,len表示每次选次是从多少个棋子中选取一个,result表示结果的存储的数组,count表示索引result的下标,NUM表示此次共需要多少个棋子。

首次的话,combine的格式为:
combine(arr,16,result,j,j)
result数组是按照下标从count-1到0的顺序来填写的。

在每次递归函数的编写中,要记得遵循四个原则:
1、基准情况;
2、不断演进;
3、用人的思考方式设计;
4、不要做重复的事情。

void combine(int *arr,int len,int *result,int count,int NUM){
int i;
for(i=len;i>=count;i--){
   result[count-1]=i-1; //此时也就是这一次是从len个棋子中选择了一个索引为i-1的棋子放在result[count-1]中。
   ifcount-1>0){
     //说明此时result数组还未满,也就是说棋子还没有挑选满
     combine(arr,i-1,result,count-1,NUM);
   }
   else{
       /*1、复制数组arr,命名为new_arr*/
       /*2、将此时满了的result数组中保存的下标依次对new_arr进行翻转;*/
       /*3、检查new_arr是否出现了纯色,若是,则保留结果,并返回*/

   }
}
}

可以看出,为了保留结果,因此combine需要再加一个形参,int *last。

④回到刚刚标注的问题,
如何标注找到/没找到?
只需要判断*last==j是否相等即可。

因此在main函数的翻转部分为:

else{
    /*复制bits,命名为new_bits*/
    for(j=1;j<=16;j++){
        int *result=(int*)malloc(sizeof(int)*j);
         combine(new_bits,16,result,j,j,&last);
                if(last==j){
                   printf("%d\n",last);
                   break;
               }
    /*new_bits已被改变,所以要还原为bits*/
        }
        if(j==17)
            printf("Impossible\n");
    }

代码:

/*
POJ 1753 Flip Game(递归+枚举)
2016/8/18
*/
#include <stdio.h>
#include <stdlib.h>

//看当前棋盘是否为纯色
int all_white_or_black(int* bits,int len){
    int i=0;
    for(i=0;i<len;i++){
    if(bits[i]!=bits[i+1])
    return 0;   
    }
    return 1;
}

//改变一个颜色时候ACTION
void change_color(int *arr,int i){
    arr[i]=!(arr[i]);
    int x=i/4; //x得出在第几行
    int y=i%4; //y得出在第几列
    if(y<3)
        arr[i+1]=!(arr[i+1]);
    if(y>0)
        arr[i-1]=!(arr[i-1]);
    if(x>0)
        arr[i-4]=!(arr[i-4]);
    if(x<3)
        arr[i+4]=!(arr[i+4]);
}

//递归判断
//这个完全用了前一篇文章的递归方法,
//只是在else语句中添加了整个图形是否为纯色的判断而已
void combine(int* arr,int len,int *result,int count,int NUM,int *last){
    int i;
    for(i=len;i>=count;i--){
        result[count-1]=i-1;
        if(count-1>0)
            combine(arr,i-1,result,count-1,NUM,last);
        else{
            int j=0;
            //在这里生成arr的副本
            int *new_arr=(int*)malloc(sizeof(int)*16);
            for(j=0;j<16;j++)
            new_arr[j]=arr[j];

            for(j=NUM-1;j>=0;j--){
                change_color(new_arr,result[j]);
            }   
            if(all_white_or_black(new_arr,16)){
                *last=NUM;
                free(new_arr);
                break;
            }
            free(new_arr);
        }

    }
}

int main(){
    char str[5];
    int bits[16];
    int count=15;
    int lines=4;
    while(lines--){
        scanf("%s",str);
        int i;
        for(i=0;i<4;i++){
           if(str[i]=='b')
           bits[count--]=1;
           else
           bits[count--]=0; 
        }
    }

    if(all_white_or_black(bits,16))
        printf("%d\n",0);
    else{
        int *new_bits=(int*)malloc(sizeof(int)*16);
        int i;
        for(i=0;i<16;i++)
            new_bits[i]=bits[i];

        int j;
        //这里last用来接收combine函数里面的NUM,即需翻转几次牌
        int last;
        for(j=1;j<=16;j++){
            int *result=(int*)malloc(sizeof(int)*j);
            combine(new_bits,16,result,j,j,&last);
            if(last==j){
                printf("%d\n",last);
                break;
            }
            //new_bits已被改变,所以要还原为bits
            for(i=0;i<16;i++)
                new_bits[i]=bits[i];
            free(result);
        }
        free(new_bits);
        if(j==17)
            printf("Impossible\n");

    }
    return 0;   
}

参考:http://www.cnblogs.com/shuaiwhu/archive/2012/04/27/2474041.html
允许转载,请注明出处。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POJ1753题目为"Flip Game",题目给出了一个4x4的棋盘,每个格子有黑色或白色,每次翻转一个格子会同时翻转它上下左右四个格子的颜色,目标是把整个棋盘都变为同一种颜色,求把棋盘变成同种颜色的最小步数。 解题思路: 一般关于棋盘变色的题目,可以考虑使用搜索来解决。对于POJ1753题目,可以使用广度优先搜索(BFS)来解决。 首先,对于每个格子,定义一个状态,0表示当前格子是白色,1表示当前格子是黑色。 然后,我们可以把棋盘抽象成一个长度为16的二进制数,将所有格子的状态按照从左往右,从上往下的顺序排列,就可以用一个16位的二进制数表示整个棋盘的状态。例如,一个棋盘状态为: 0101 1010 0101 1010 则按照从左往右,从上往下的顺序把所有格子的状态连接起来,即可得到该棋盘的状态为"0101101001011010"。 接着,我们可以使用队列来实现广度优先搜索。首先将初始状态加入队列中,然后对于队列中的每一个状态,我们都尝试将棋盘上的每个格子翻转一次,生成一个新状态,将新状态加入队列中。对于每一个新状态,我们也需要记录它是从哪个状态翻转得到的,以便在得到最终状态时能够输出路径。 在搜索过程中,我们需要维护每个状态离初始状态的步数,即将该状态转换为最终状态需要的最小步数。如果我们找到了最终状态,就可以输出答案,即最小步数。 代码实现:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值