題意:格子有兩面,1表示黑色格子,0表示白色格子,奶牛每次能夠踩一個格子,踩到的格子和它周圍的上下左右格子都會翻面,也便是顏色改變,
問:能不能踩有限個格子,使得全部格子都變成白色,若是能,求踩格子次數的方案,而且要求字典序(1)最小的那一個方案。
(1):字典序,能夠百度一下哦。ios
思路:
純暴力枚舉:
M * N個格子,每一個格子翻和不翻2種可能,時間複雜度O(2^M * N),顯然不行。
改進的暴力方法:
咱們想:一個格子的狀態取決於自己的顏色加上自己翻與不翻和四周的四個格子翻與不翻,
再想,爲了讓上面的思考實現並且有條理,不如咱們從第一行開始判斷,直到最後一行,若是
所有是白色,說明該方法能夠,不然不行。
那咱們能夠枚舉第一行的全部翻與不翻的狀況,假設一行有M個格子,那麼第一行的狀況有2^M,
,按照第一行的顏色狀況,判斷第二行每一個格子翻與不翻使得第一行所有變成白色,。。。以此
類推,直到最後一行。
那時間複雜度差很少是O(M * N *2^M),M∈[1,15],可行。
那具體怎麼作呢,
咱們須要三個數組
mp[N][N]表示原來的格子狀況
cur[N][N]表示當前每一個格子翻與不翻的狀況,1表示翻
ans[N][N]表示最後01矩陣的符合題目的答案
一個min_t記錄最小翻轉次數
一個tmp_t,某個方法的當前翻轉次數數組
其實,一個棋子翻與不翻,咱們就是爲了改變它上面那一個格子的狀態,那它上面那個格子的狀態怎麼肯定呢,上面說了,那咱們能夠這麼判斷:上面那一個格子的顏色加上自身翻與不翻和上左右翻與不翻的狀況,因而咱們能夠肯定上面那一個格子的狀態,因而咱們就能夠判斷該格子也就是下面那個格子翻與不翻來把上面的格子變白色,每一個格子都這麼作,那麼題目就變得簡單了。spa
1 #include <iostream> 2 #include <string.h> 3 #include <algorithm> 4 using namespace std; 5 6 #define inf (1LL << 31) - 1 7 #define rep(i,j,k) for(int i = (j); i <= (k); i++) 8 #define rep_(i,j,k) for(int i = (j); i < (k); i++) 9 #define per(i,j,k) for(int i = (j); i >= (k); i--) 10 #define per_(i,j,k) for(int i = (j); i > (k); i--) 11 12 const int N = 20; 13 int mv_x[] = { 0, 0, 0, -1 }; 14 int mv_y[] = { 0, -1, 1, 0 }; 15 int ans[N][N]; 16 int cur[N][N]; //記錄的是翻與不翻的狀況 17 int mp[N][N]; 18 int min_t; 19 int tmp_t; 20 int n, m; 21 22 inline void input(){ 23 rep_(i, 0, n)rep_(j, 0, m){ 24 cin >> mp[i][j]; 25 } 26 } 27 28 inline bool ok(int x,int y){ 29 return (x >= 0 && x < n && y >= 0 && y < m); 30 } 31 32 int search(int x, int y){ 33 int k = mp[x][y]; //上面格子的顏色(1) 34 35 rep(p, 0, 3){ //上面格子自身和上左右的翻與不翻狀況(2) 36 int dx = x + mv_x[p]; 37 int dy = y + mv_y[p]; 38 39 if (ok(dx, dy)){ //在地圖界限內 40 k += cur[dx][dy]; 41 } 42 } 43 44 //若是(1) + (2) 爲奇數說明上個格子爲黑色返回1,不然是白色返回0 45 return k & 1; 46 } 47 48 void work(){ 49 50 rep_(i, 1, n){ 51 rep_(j, 0, m){ 52 if (search(i - 1, j)){ //上個格子的狀況 53 //上個格子是黑色 54 tmp_t++; //該格子翻轉,使得上的格子變白色 55 cur[i][j] = 1; //記錄該格子的翻轉狀況 56 } 57 } 58 } 59 60 //對最後一行檢查,是否都是白色,不是直接結束該狀況分支 61 rep_(j, 0, m){ 62 if (search(n - 1, j)) return; 63 } 64 65 //記錄最優解 66 //咱們枚舉第一行的狀況,且從000000000000000開始枚舉 67 //那麼每個新的翻轉次數必定是該反轉次數字典序最小的 68 if (tmp_t < min_t){ 69 min_t = tmp_t; 70 memcpy(ans, cur, sizeof(cur)); 71 } 72 } 73 74 //輸出答案 75 inline void get_ans(){ 76 77 if (min_t == inf){ 78 cout << "IMPOSSIBLE" << endl; 79 return; 80 } 81 82 rep_(i, 0, n){ 83 cout << ans[i][0]; 84 rep_(j, 1, m) cout << " " << ans[i][j]; 85 cout << endl; 86 } 87 } 88 89 int main(){ 90 91 ios::sync_with_stdio(false); 92 cin.tie(0); 93 94 cin >> n >> m; 95 96 input(); //讀取數據 97 98 min_t = inf; 99 // cout << min_t << endl; 100 rep_(i, 0, 1LL << m){ //第一行有 2^M種狀況 101 102 tmp_t = 0; //每種狀況的次數初始化 103 memset(cur, 0, sizeof(cur)); //每種狀況初始化 104 105 rep_(j, 0, m){ 106 int t = (i >> j) & 1; //二進制枚舉第一行翻與不翻狀況 107 //好比一行有15個格子就是2^15種狀況 108 //000000000000000 109 //能夠表示0~2^15 - 1,就是2^15種狀況 110 //每一位0或者1表示翻與不翻 111 112 cur[0][m - 1 - j] = t; //每一位對1來與(&),而後記錄 113 114 if (t) tmp_t++; //若是t是1,那就是第一行某個格子翻 115 } 116 117 work(); 118 } 119 get_ans(); 120 121 return 0; 122 }