POJ 3279 Flip title 状态压缩+暴力
题意
有(M*N) (<=15)的01矩阵,定义操作(f[i][j]):使第i行j列的元素自身以及上下左右共五个格子翻转,找出翻转次数最少的方案,如果有多个解,输出字典序最小的方案
思路
N的范围只有15,构造状态数组(r[M]),其值为矩阵每一行的状态,比如样例输入第一行1001,就可以将其表示为1001(2) = 9。定义操作矩阵(b[M]),表示翻转对应位,比如0001(2) = 1,表示第1,2,3位不翻转,第四位翻转。
枚举第一行的(2^n)种情况,对第一行进行操作后,依次处理其他行,每次将其上面一行变为全0,处理完毕后如果最后一行也为0,则得到一个解。
显然,对于(a[i][j]=1), 我们只需要令(b[i+1][j]=1), 就能让(a[i])的1被翻转成0。设第(i)行为(a_i) 则对第(i+1)行需要进行的操作就是(a_i) ,我们只要把路径最小的(a_i)存起来,因为我们是按照字典序枚举的,所以找到的第一个解就是(Ans) 。
以下给出用位运算压缩的解法,作为比较,数组解法的时间消耗要远大于位运算,实测中大约在3倍时间差距左右。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAX = 15 + 5;
int n, m;
int r[MAX]; //原始矩阵
int a[MAX]; //临时矩阵
int b[MAX]; //操作矩阵
int ans[MAX]; //结果矩阵
int cnt;
void flip(int i, int j) { //对a[i][j]翻转
cnt++;
a[i] ^= 1 << j;
a[i] ^= 1 << (j - 1);
a[i] ^= 1 << (j + 1);
a[i + 1] ^= 1 << j;
a[i - 1] ^= 1 << j;
}
void solve(int i, int x) { //对a[i]进行操作x
b[i] = x;
for (int j = 1; j <= m; j++) {
if (x & (1 << j)) {
flip(i, j);
}
}
}
int main(void) {
while (scanf("%d%d", &n, &m) != EOF) {
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(r, 0, sizeof(r));
int t;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &t);
if (t) {
r[i] |= 1 << j; //对应位置1
}
}
}
int len = INF;
int bound = 1 << m;
for (register int state = 0; state < bound; state++) {
cnt = 0;
for (int i = 1; i <= n; i++) {
a[i] = r[i];
}
int i = state << 1; //做了一个偏移
solve(1, i);
for (int j = 2; j <= n; j++) {
solve(j, a[j - 1]);
}
int flag = (a[n] >> 1) % bound; //去掉偏移量
if (cnt < len && !flag) {
len = cnt;
for (int k = 1; k <= n; k++) {
ans[k] = b[k];
}
}
}
if (len == INF) {
printf("IMPOSSIBLE
");
} else {
for (int i = 1; i <= n; i++) {
printf("%d", ans[i] & (1 << 1) ? 1 : 0);
for (int j = 2; j <= m; j++) {
printf(" %d", ans[i] & (1 << j) ? 1 : 0);
}
printf("
");
}
}
}
return 0;
}