POJ 3279 - Fliptile - [状压+暴力枚举]
题目链接:http://poj.org/problem?id=3279
Sample Input
4 4 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1
Sample Output
0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0
题意:
给出 $M$ 行 $N$ 的矩阵,每个元素只为 $0$ 或者 $1$,分别代表白块和黑块,
每次可以翻转一个元素,使得它可以从黑变白或者从白变黑,但是上下左右相邻的四个元素也会跟着翻转,
求最少翻转多少个元素,可以使得全部元素均变成 $0$。
给出需要反转的元素,若有多个答案,给出字典序最小的,若无答案,输出"IMPOSSIBLE"。
题解:
首先,显然的:翻转只有 $0$ 次和 $1$ 次,多了没用;其次,翻转顺序无关性,结果与翻转方块的顺序无关,只与翻转那几个方块有关。
如果从上往下一行一行看,那么当第 $i$ 行确定了如何翻转后,第 $i$ 行上如果剩下来若干个黑块,那么只能靠翻转第 $i+1$ 行来将其变成白块,
这就注定了:一旦确定了第 $1$ 行如何翻转,后面的所有行如何翻转都被确定。
所以只要枚举第 $1$ 行所有翻转方案即可,列数不超过15,可以使用状压。
另外,由于第一行的翻转方案一旦给定,后面三行就是固定的,所以翻转方案的字典序可以只看第一行,
所以方案可以存成一个结构体,在存储方案数组的同时,一并存储总的翻转次数,以及,第一行翻转方案(存成二进制数);在最后进行排序,并输出第一个即可。
AC代码:
#include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<algorithm> using namespace std; const int maxn=18; int m,n; int mp[maxn][maxn],tmp[maxn][maxn]; struct Ans{ int idx; int times; int f[maxn][maxn]; Ans() { this->times=0; memset(f,0,sizeof(f)); } }; bool cmp(Ans a,Ans b) { if(a.times==b.times) return a.idx<b.idx; return a.times<b.times; } inline void flip(int i,int j) { if(i>1) tmp[i-1][j]^=1; if(i<m) tmp[i+1][j]^=1; if(j>1) tmp[i][j-1]^=1; if(j<n) tmp[i][j+1]^=1; tmp[i][j]^=1; } inline bool allwhite() { for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) if(tmp[i][j]) return 0; return 1; } int main() { cin>>m>>n; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) cin>>mp[i][j]; vector<Ans> v; for(int sta=0;sta<(1<<n);sta++) { for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) tmp[i][j]=mp[i][j]; Ans ans; for(int j=1;j<=n;j++) if(sta&(1<<(n-j))) flip(1,j), ans.f[1][j]=1, ans.times++; for(int i=2;i<=m;i++) for(int j=1;j<=n;j++) if(tmp[i-1][j]) flip(i,j), ans.f[i][j]=1, ans.times++; if(allwhite()) { ans.idx=sta; v.push_back(ans); } } sort(v.begin(),v.end(),cmp); if(v.size()>0) for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) printf("%d%c",(*v.begin()).f[i][j],j<n?' ':'\n'); else printf("IMPOSSIBLE\n"); }
时间复杂度:$O\left( {2^n mn} \right)$;后面的对所有可行方案的排序,认为能够达成目标的翻转方案远小于 $2^n$,不考虑其时间复杂度)。
转载请注明出处:https://dilthey.cnblogs.com/