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$,不考虑其时间复杂度)。

posted @ 2018-09-22 20:20  Dilthey  阅读(576)  评论(0编辑  收藏  举报