Flip Game
Description
Flip game is played on a rectangular 4x4 field with two-sided pieces placed on each of its 16 squares. One side of each piece is white and the other one is black and each piece is lying either it's black or white side up. Each round you flip 3 to 5 pieces, thus changing the color of their upper side from black to white and vice versa. The pieces to be flipped are chosen every round according to the following rules:
![]() bwbw wwww bbwb bwwb Here "b" denotes pieces lying their black side up and "w" denotes pieces lying their white side up. If we choose to flip the 1st piece from the 3rd row (this choice is shown at the picture), then the field will become: bwbw bwww wwwb wwwb The goal of the game is to flip either all pieces white side up or all pieces black side up. You are to write a program that will search for the minimum number of rounds needed to achieve this goal. Input
The input consists of 4 lines with 4 characters "w" or "b" each that denote game field position.
Output
Write to the output file a single integer number - the minimum number of rounds needed to achieve the goal of the game from the given position. If the goal is initially achieved, then write 0. If it's impossible to achieve the goal, then write the word "Impossible" (without quotes).
Sample Input bwwb
bbwb
bwwb
bwww Sample Output 4
Source |
題目大意:
有$4\times 4$的棋盤,上面的棋子一面是黑的,一面是白的。規定翻轉一個棋子的同時也要翻轉它的上、下、左、右的棋子,問給定一個棋盤的棋子狀態,至少需要翻轉多少個棋子,能使得所有棋子都是白的或黑的(面在上)。
基本思路:
一、暴力搜索出奇跡:
1、首先要明確一點:這個翻棋子就像按位異或一樣,如果一列數里面有兩個數是相等的,那把它們全都異或起來,根據結合率就相當於在這過程中有一個數與自身異或,結果為0。把這兩個相等的數去掉對最終異或結果是沒有影響的。同樣看這個翻棋子,它只有兩個面,翻一次由黑面在上轉為白面在上,或者白轉黑,但翻兩次就恢復原來的樣子了,翻沒翻是一樣的,除非再翻多一次,甚至是奇數次,但這沒有必要也沒有意義。
2、數據規模小,考慮我們最少不翻任何棋子(初始狀態就是全白或全黑),最多只翻16個棋子(再翻就有棋子被翻兩次了),因此總的狀態數為$C^{0}_{16}+C^{1}_{16}+\cdots+C^{16}_{16}=2^{16}$,直接枚舉或搜索即可。
3、考慮要求的是最小值,因此從翻$0$個開始,搜索翻$i$個,直到翻$16$個,中間搜索到即為最小值,翻16個都不滿足即輸出$Impossible$。
4、由於棋子都只有黑、白兩面,可以用0、1表示,因此可以位壓縮成一個數字來進行判斷,翻棋子的操作可使用位運算,有兩種方法:
方法一:每一行壓縮一個數字,對第$i$行第$j$列棋子進行翻轉,比如$j=2$,則$i-1$、$i+1$行的棋子應該和4(0100)相異或(與1異或切換狀態,與0異或不改變),而第$i$行棋子應與14(1110)相異或。
方法二:只有16個棋子,一個int型變量就能存下這16個0/1了,所以可以直接壓縮成一個數字。如$i=2, j=2$,則與20032(0100 1110 0100 0000)相異或,不過手算16位的狀態是比手算4位煩一點點。
5、搜索過程中要注意搜過的位置不需要再搜了,所以在函數里控制一下$i$、$j$,當然實現並不唯一。還要注意如果沒搜成功,把棋子再翻(flip)一遍,這樣就能恢復原樣了。不需要memcpy,那是很蠢的做法。
方法一代碼:

1 #include <stdio.h>
2
3 int field[6]={0}; 4 int state[][4]={{8,4,2,1},{12,14,7,3}}; 5
6 void read() { 7 for(int i=1; i<=4; i++) { 8 for(int j=1; j<=4; j++) { 9 field[i]<<=1; 10 if(getchar()=='b') 11 field[i]|=1; 12 } 13 getchar(); 14 } 15 } 16
17 void flip(int i, int j) {--j; 18 field[i-1]^=state[0][j]; 19 field[i] ^=state[1][j]; 20 field[i+1]^=state[0][j]; 21 } 22
23 bool check() { 24 return (field[1]==0||field[1]==15) 25 && field[1]==field[2] 26 && field[2]==field[3] 27 && field[3]==field[4]; 28 } 29
30 bool find(int n, int i, int j) { 31 if(n==0) return check(); 32 j+=1; if(j>4) i+=1, j=1; 33 if(i>4) return false; 34 for(; i<=4; i++) { 35 for(; j<=4; j++) { 36 flip(i, j); 37 if(find(n-1,i,j)) 38 return true; 39 flip(i, j); 40 } 41 j=1; 42 } 43 return false; 44 } 45
46 void work() { 47 for(int i=0; i<=16; i++) 48 if(find(i,1,0)) { 49 printf("%d\n", i); 50 return; 51 } 52 puts("Impossible"); 53 } 54
55 int main() { 56 read(); 57 work(); 58 return 0; 59 }
方法二代碼(首先打個表):

1 void init() { 2 for(int i=1; i<=16; i++) { 3 int v=0, k=1<<(i-1); v|=k; 4 if((i+1)%4!=1) v|=k<<1; 5 if((i-1)%4!=0) v|=k>>1; 6 if(i>4) v|=k>>4; 7 if(i<13) v|=k<<4; 8 printf("%d,",v); 9 } 10 }
然后就可以拿表去水了,當然直接判斷也是可以的,丑。

1 #include <stdio.h>
2
3 int field; 4 int state[]={19,39,78,140,305,626,1252,2248,4880,10016,20032,35968,12544,29184,58368,51200}; 5
6 void read() { 7 for(int i=0; i<4; i++) { 8 for(int j=0; j<4; j++) { 9 field<<=1; 10 if(getchar()=='b') 11 field|=1; 12 } 13 getchar(); 14 } 15 } 16
17 void flip(int i) { 18 field^=state[i]; 19 } 20
21 bool check() { 22 return field==0x0000||field==0xFFFF; 23 } 24
25 bool find(int n, int i) { 26 if(n==0) return check(); 27 //if(i>=16) return false;
28 for(; i<16; i++) { 29 flip(i); 30 if(find(n-1,i+1)) 31 return true; 32 flip(i); 33 } 34 return false; 35 } 36
37 void work() { 38 for(int c=0; c<=16; c++) 39 if(find(c,0)) { 40 printf("%d\n", c); 41 return; 42 } 43 puts("Impossible"); 44 } 45
46 int main() { 47 read(); 48 work(); 49 return 0; 50 }
二、枚舉
1、這題應該容易想搜索,當然枚舉也是比較簡單能想到的。我們還是像前面方法二那樣位壓縮成一個數,如果不能壓成一個int的話這題當然也用不了枚舉。需要考慮的是如何實現$C^i_{16}$,也就是$16$個選$i$個$(i\in [0, 16])$,考慮我選哪幾個棋子也表示成0/1,選擇翻轉的棋子我用1表示,比如要選擇第1個、第3個、第5個和第6個,那就是11 0101的狀態。這樣枚舉就很方便了,枚舉值范圍0x0000~0xFFFF。
2、同樣像上面方法二那樣打個表,對於每個枚舉的狀態,用位與運算求出哪個位是1(哪個棋子要翻轉),然后根據打表的數據對輸入的棋盤進行異或運算。過程中對翻轉后棋盤全黑或全白的情況求最少翻轉數。
3、可以順手再打 1<<0 ~ 1<<15 的表。

1 #include <stdio.h>
2
3 int field; 4 int state[]={19,39,78,140,305,626,1252,2248,4880,10016,20032,35968,12544,29184,58368,51200}; 5 int bit[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768}; 6
7 void read() { 8 for(int i=0; i<4; i++) { 9 for(int j=0; j<4; j++) { 10 field<<=1; 11 if(getchar()=='b') 12 field|=1; 13 } 14 getchar(); 15 } 16 } 17
18 bool check() { 19 return field==0x0000||field==0xFFFF; 20 } 21
22 int minn=0xFF; 23 void work() { 24 for(int flip=0; flip<=0xFFFF; flip++) { 25 int temp=field, cnt=0; 26 for(int i=0; i<16; i++) 27 if(flip&bit[i]) {// flip&(1<<i)
28 field^=state[i]; 29 ++cnt; 30 } 31 if(check()&&minn>cnt) minn=cnt; 32 field=temp; 33 } 34 } 35
36 void print() { 37 if(minn==0xFF) puts("Impossible"); 38 else printf("%d\n", minn); 39 } 40
41 int main() { 42 read(); 43 work(); 44 print(); 45 return 0; 46 }
三、高斯消元法
1、基本想法是,令a=棋盤狀態矩陣,b=最終各棋子的狀態,ax=b解出x=要翻轉的棋子,數一下x里面1的數量就是翻轉的棋子數了。因為最終狀態可以是全黑或全白,因此需要對b取兩次值,做兩次消元。
2、但是你會發現,這題會經常出現無窮多解的情況,也就是存在自由變元。因此需要枚舉or搜索這些自由變元的值。
(代碼目前沒交,待更新)