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
}
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) :把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 <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
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 <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
}
return;
}
//把當前行都翻成同種顏色
int x,y;
/*這里注意,i為current-1是在對上一行做判斷,因為上一行的狀況,限制這一行翻動方案*/
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) )先被執行過以后,source串里面(i,j)位置和1作與,其他位置被置0,然后>>(i*4+j),(I,j位置的值被移到了最低位,此時source的值不是0,就是1,也就是(i,j)位置的值,和flag作異或,就知道前一行中是否存在與目標翻動色不一致的顏色,如果有則這個棋子下方,也就是當前行的這個棋子必須進行翻動*/
,
for (int k = 0; k <5; k++){//當前,上下左右都得翻動
x=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. 按廣度優先搜索時,考慮到每行對下一行的限制,大大縮小運算量