题目链接:https://vjudge.net/problem/POJ-3279
题意:格子有两面,1表示黑色格子,0表示白色格子,奶牛每次可以踩一个格子,踩到的格子和它周围的上下左右格子都会翻面,也即是颜色改变,
问:能不能踩有限个格子,使得所有格子都变成白色,如果能,求踩格子次数的方案,并且要求字典序(1)最小的那一个方案。
(1):字典序,可以百度一下哦。
思路:
纯暴力枚举:
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,某个方法的当前翻转次数
其实,一个棋子翻与不翻,我们就是为了改变它上面那一个格子的状态,那它上面那个格子的状态怎么确定呢,上面说了,那我们可以这么判断:上面那一个格子的颜色加上自身翻与不翻和上左右翻与不翻的情况,于是我们可以确定上面那一个格子的状态,于是我们就可以判断该格子也就是下面那个格子翻与不翻来把上面的格子变白色,每个格子都这么做,那么题目就变得简单了。
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 }