import java.util.Scanner;
 
public class Main{
  static  intsteps=Integer.MAX_VALUE;
 //(x,y)坐标合起来就是中心点及上下左右坐标啦!
     static  int[] dx={0,0,0,1,-1};
     static  int[]dy={0,1,-1,0,0}; 
 
    /*
     * 把每一行的状态和整个状态都以2进制表示,四个2进制数排成一行,组成整个状态。
     * 1010
     * 0000
     * 1101
     * 1001
      *如图的状态表示为:1010000011011001
     * @param x横坐标点(注意:坐标系右下为(0,0)左上为(3,3))
     * @param y纵坐标点
     * @param source(整个状态如上面的1010000011011001)
     * @return 改变确定位置的状态,如1变成0或者0变成1
     * */ 
 
  public static int flip(int x, int y, int source){
          if(x >= 0 && x < 4&& y >= 0 && y < 4) 
           source ^= 1 << (x * 4 + y);
         return source;
    } 
 
 
    /*
     * @param current当前行
     * @param num 回合数
     * @param source 原数据,比如:1010000011011001
     * @param flag 标志如果数据源当前位的状态不为flag,则翻动
     * */
 
    public static void dfs(int current,int num,int source,int flag){
         //如果最后一行已经翻完
         if(current==4){
              if(source==0xffff||source==0){
                  //已经完成了任务
                 steps=num<steps?num:steps;
              }
            return;
        }  
 
      //把当前行都翻成同种颜色
        int x,y;
        for (int i = current-1,j=0; j < 4; j++) {//每行有四个,翻或者不翻,所以需要四次
            if( (((source& (1 << (i*4+j) ))>>(i*4+j)) ^ flag)==1 ){
                  /*source& (1 <<(i*4+j) )>>(i*4+j) :把source中的(i,j)的状态取出来*/
                for (int k = 0; k <5; k++){//当前,上下左右都得翻动
                     x=current+dx[k];
                     y=j+dy[k];
                      source=flip(x, y,source);
                 }
                 num++;
            } 
       } 
 
          //翻下一行
        dfs(current+1, num, source, flag);
     } 
 
 
    /*  第一行共有16种翻法(翻,翻,翻,翻)(翻,翻,翻,不翻)。。。(不翻,不翻,不翻,不翻)
     * */
    public static int solve(int source){ 
        for (int i = 0; i < 16; i++) { 
            int num=0,temp=source,x,y; 
            for (int j = 0; j < 4; j++) { // 这个循环是翻第一行
                 if((i&(1 <<j))>0){ 
                     for (int k = 0; k < 5;k++) {//当前,上下左右都得翻动 
                         x=0+dx[k]; 
                         y=j+dy[k]; 
                         temp=flip(x, y,temp); 
                     } 
                     num++; 
                 } 
            } 
 
             dfs(1, num, temp, 0);  //全部翻成白色
 
           dfs(1, num, temp, 1);  //全部翻成黑色
        } 
        return steps==Integer.MAX_VALUE?-1:steps; 
    } 
 
 public static void main(String[] args) { 
        Scanner scanner=new Scanner(System.in); 
        int source=0; 
        String string=""; 
        for (int i = 0; i < 4; i++) { 
            string+=scanner.nextLine().trim(); 
        } 
 
       // System.out.println(string);
        for (int i = 0; i <string.length(); i++) { 
                 source=(source < <1)+(string.substring(i, i+1).equals("b")?1:0); 
        } 
      // System.out.println(Integer.toBinaryString(source));
        if(solve(source)!=-1){ 
            System.out.println(steps); 
        }else { 
            System.out.println("Impossible"); 
 
        } 
    } 
 }




代码并非原创,摘自网上

 

这里对我原来看的时候有点疑惑的几个地方进行一下说明,一作记录,而来希望能够给其他遇到同样困惑的人带来点帮助

 

首先需要注意的是代码表示的坐标点(注意:坐标系右下为(0,0)左上为(3,3)),这一点在阅读代码时一不小心忘记,就会陷入困惑。

1.基本的思想是枚举,但是如果全部枚举的话会有2^16的方案,这段代码最精妙的地方在于在枚举过程排除了一些不可能使棋盘翻至同色的方案,使得运算量大大减小,这也是值得借鉴到平时编程的思想。

 

           首先对第一行的翻法进行枚举,这里有16种方案

                  

/*  第一行共有16种翻法(翻,翻,翻,翻)(翻,翻,翻,不翻)。。。(不翻,不翻,不翻,不翻)
     * */ 
    public static int solve(int source){ 
        for (int i = 0; i < 16; i++) { 
            int num=0,temp=source,x,y;  
            for (int j = 0; j < 4; j++) { // 这个循环是翻第一行
                 if((i&(1 <<j))>0){  
                     for (int k = 0; k < 5;k++) {//当前,上下左右都得翻动  
                         x=0+dx[k];  
                         y=j+dy[k];  
                         temp=flip(x, y,temp);  
                     }  
                     num++;  
                 }  
            }

            然后对剩下的几行进行枚举

然后对第二行的方案进行遍历,这里是最关键的思想,在第一行的翻动方案确定以后,实际上对第二行的翻动方案已经有所限制。在遍历测试第二行的翻动方案时,如果第一行还不是同色,比如希望把棋子全部翻成白色时,第一行中还有棋子是黑色,则这个棋子下方的棋子,也就是在第二行的这个棋子必须被翻动,否则的话,在四行的方案都确定后,第一行的这颗黑色棋子,必然还是黑色。以此类推,每一行的方案,都对下一行的翻动作出了限制。所以这样就大大缩小了运算量

dfs(1, num,temp, 0);  //全部翻成白色,   这里第一个参数1,表示的是BFS第二行,不是第一行,第一行的16种方案已经枚举过
       dfs(1, num, temp, 1);  //全部翻成黑色
 
这里是BFS函数
  /* 
     * @param current当前行  
     * @param num 回合数
     * @param source 原数据,比如:1010000011011001
     * @param flag 标志如果数据源当前位的状态不为flag,则翻动
     * */
 
    public static void dfs(int current,int num,int source,int flag){
         //如果最后一行已经翻完
         if(current==4){
              if(source==0xffff||source==0){
                  //已经完成了任务
                 steps=num<steps?num:steps;
              } 
            return; 
        }   
 
      //把当前行都翻成同种颜色 
        int x,y; 
这里注意,i为current-1是在对上一行做判断,因为上一行的状况,限制这一行翻动方案*/
current-1,j=0; j < 4;j++) {//每行有四个,翻或者不翻,所以需要四次
            if( (((source& (1 << (i*4+j) ))>>(i*4+j)) ^ flag)==1 ){

  /*这句话括号比较多,执行顺序看清楚就好source& (1 << (i*4+j) )先被执行过以后,source串里面(i,j)位置和1作与,其他位置被置0,然后>>(i*4+j),(I,j位置的值被移到了最低位,此时source的值不是0,就是1,也就是(i,j)位置的值  ,和flag作异或,就知道前一行中是否存在与目标翻动色不一致的颜色,如果有则这个棋子下方,也就是当前行的这个棋子必须进行翻动*/

,
                for (int k = 0; k <5; k++){//当前,上下左右都得翻动 
current+dx[k];    //这里是current,而不是i,是对当前行的操作
                     y=j+dy[k];
                      source=flip(x, y,source); 
                 } 
                 num++; 
            }  
       }  
 
          //翻下一行 
        dfs(current+1, num, source, flag); 
     }

 

 

最容易困惑的就是上面这一点点了

这个题目中值得借鉴的两个亮点就是

1利用位串source来对整个棋盘进行表示,进行最终状态的判断简化为if(source==0xffff||source==0)

2. 按广度优先搜索时,考虑到每行对下一行的限制,大大缩小运算量