题意:格子有两面,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 }