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个下标,来进行对应的翻转。
for(j=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]中。
if(count-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
允许转载,请注明出处。