题意:
给出4*4的棋盘,只有黑棋和白棋,问你最少几步可以使棋子的颜色一样。
游戏规则是:如果翻动一个棋子,则该棋子上下左右的棋子也会翻一面,棋子正反面颜色相反。
思路:
都是暴搜枚举。
第一种方法:暴力dfs枚举
棋子只有最多翻一次,因为翻两次后结果和不翻是一样的,所以整个棋盘最多翻16次。
用step代表翻转的次数,当翻转了step次时,就看一下整个棋盘是否是清一色的。
当棋盘是清一色的时候就直接输出step,得到的就是最少翻转次数使棋盘清一色。
第二种方法:利用位运算来优化
因为棋子不是白就是黑,所以可以用0和1来表示。
然后为每一个棋子编号,并计算出该棋子若翻转会影响到的棋子的位置,可以把它们都看成是二进制的。
例如 棋子位置是 第二行第二列 则翻转该棋子会影响到的棋子位置是
0 0 0 0 0 1 0 0
0 1 0 0 1 0 1 0
0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0
二进制表示 0000 0100 0000 0000 0100 1010 0100 0000
当计算最小值时可以预处理一下,即计算出由全白到该方案最少需要步数以及全黑到该方案需要的最少步数。
然后每一种方案都可以通过枚举 dp[j] = min(dp[j], dp[i]+1)来得到,dp[i]表示左上的状态,dp[j]表示右上的状态。
j = 影响的棋子位置^i
再优化之:其实,所以用dp[(1<<16-1)^i]得到的状态j即是状态i到全黑的最少步数, 这样就可以减少一个数组的开销了。
第三种方法:利用棋子翻转的奇偶次数
预处理时计算出改变该棋子会影响的棋子位置
然后得到棋盘状态的二进制形式记做p
然后枚举翻棋子的情况,总共有2^16-1种,看一看如果翻这些牌并且改变了相应受影响位置的牌棋盘后会不会变成清一色
并记录最少的步数
Tips:
位运算的优先级比较小,所以应该st应该= (1<<16)-1,而不是1<<16-1
Code:
1 #include <stdio.h> 2 #include <cstring> 3 4 int dir[5][2] = {0, 0, -1, 0, 0, 1, 1, 0, 0, -1}; 5 6 bool G[6][6] = {false}; 7 bool flag; 8 int step; 9 void flip(int r, int c) 10 { 11 for (int i = 0; i < 5; ++i) { 12 int rr = dir[i][0]+r, cc = dir[i][1]+c; 13 G[rr][cc] = !G[rr][cc]; 14 } 15 } 16 17 bool check() 18 { 19 for (int i = 1; i <= 4; ++i) 20 for (int j = 1; j <= 4; ++j) 21 if (G[i][j] != G[1][1]) return false; 22 return true; 23 } 24 25 void dfs(int x, int y, int depth) 26 { 27 if (depth == step) { 28 flag = check(); 29 return; 30 } 31 if (flag || y == 5) return; 32 33 flip(x, y); 34 if (x < 4) 35 dfs(x+1, y, depth+1); 36 else dfs(1, y+1, depth+1); 37 38 flip(x, y); 39 if (x < 4) 40 dfs(x+1, y, depth); 41 else dfs(1, y+1, depth); 42 } 43 44 int main() 45 { 46 //freopen("in.txt", "r", stdin); 47 char c; 48 for (int i = 1; i <= 4; ++i) { 49 for (int j = 1; j <= 4; ++j) { 50 scanf("%c", &c); 51 if (c == 'w') G[i][j] = true; 52 else G[i][j] = false; 53 } 54 getchar(); 55 } 56 for (step = 0; step <= 16; ++step) { 57 dfs(1, 1, 0); 58 if (flag) break; 59 } 60 if (flag) printf("%d\n", step); 61 else puts("Impossible"); 62 return 0; 63 }