Fliptile(反转问题状态压缩)poj 3279.

题目:

Farmer John knows that an intellectually satisfied cow is a happy cow who will give more milk. He has arranged a brainy activity for cows in which they manipulate an M× N grid (1 ≤ M ≤ 15; 1 ≤ N ≤ 15) of square tiles, each of which is colored black on one side and white on the other side.

As one would guess, when a single white tile is flipped, it changes to black; when a single black tile is flipped, it changes to white. The cows are rewarded when they flip the tiles so that each tile has the white side face up. However, the cows have rather large hooves and when they try to flip a certain tile, they also flip all the adjacent tiles (tiles that share a full edge with the flipped tile). Since the flips are tiring, the cows want to minimize the number of flips they have to make.

Help the cows determine the minimum number of flips required, and the locations to flip to achieve that minimum. If there are multiple ways to achieve the task with the minimum amount of flips, return the one with the least lexicographical ordering in the output when considered as a string. If the task is impossible, print one line with the word "IMPOSSIBLE".

Input

Line 1: Two space-separated integers: M and N 
Lines 2.. M+1: Line i+1 describes the colors (left to right) of row i of the grid with N space-separated integers which are 1 for black and 0 for white

Output

Lines 1.. M: Each line contains N space-separated integers, each specifying how many times to flip that particular location.

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

将矩阵中全部变为0,最少的反转次数。

通过第一行的状态来判断,第一行的状态决定了以后的状态。所以先枚举第一行的状态,然后,从下面一行不断的将上边一行的为1的同列翻转。统计最后一行是否全是0来判断此种方案是否可行,其实不断的美剧第一行就是为了求出最小的解。

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int maxn = 20;
const int INF = 0x3f3f3f3f;
int mapp[maxn][maxn];//存图
int n,m;
int ans = INF;
int a[maxn][maxn];//存最优解
int b[maxn][maxn];//存翻转方案
int dir[5][2]={ { -1, 0 }, { 1, 0 }, { 0, 0 }, { 0, -1 }, { 0, 1 } };
bool getcolour(int x,int y)//得到该位置上的颜色。也就是说,周围的都翻转完的颜色。
{
    int res = mapp[x][y];
    for(int i = 0;i < 5;i ++)
    {
        int fx = x + dir[i][0];
        int fy = y + dir[i][1];
        if(fx >= 0&&fx <= m&&fy >= 0&& fy <= n)
        {
            res += b[fx][fy];
        }
    }
    return res&1;//如果这个位置被反转了奇数次,就回到原来的状态。
}

int solve()
{
    int res = 0;
    for(int i = 1;i < n ;i ++)//从第2行一直统计到n-1行。
        for(int j = 0; j < m; j ++)
        if(getcolour(i - 1,j))b[i][j] = 1;
    for(int j = 0;j < m;j ++)//判断最后一行是否全是白的。
    {
        if(getcolour(n-1,j)) return INF;
    }
    for(int i = 0;i < n;i ++)//记录总的反转次数。
        for(int j = 0;j < m;j ++)
        res += b[i][j];
    return res;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 0;i < n;i ++)
        for(int j = 0; j < m;j ++)
        scanf("%d",&mapp[i][j]);
    for(int i = 0;i < (1 << m);i ++)//枚举第一行的所有情况,第一行的状态决定了以后所有行。
    {                               //通过枚举第一行的不同值,来找到反转次数最少的那一组。
        memset(b,0,sizeof(b));
        for(int j = 0;j < m;j ++)
            b[0][j] = i >> (m - j - 1) & 1;
        int t = solve();
        if(t < ans)
        {
            ans = t;
            memcpy(a,b,sizeof b);
        }
    }
    if (ans == INF)printf("IMPOSSIBLE\n");
    else {
        for(int i = 0;i < n;i ++)
        {
            for(int j = 0;j < m;j ++)
            printf("%d ",a[i][j]);
            printf("\n");
        }
    }
    return 0;
}

 很早以前写的一个,不过这次看到这个题又不会了,看以前的代码也看不懂。

/*****************************************\
首先处理第一行,第一行决定了以后每一行的状态;
将第一行中所有为1的翻转,该行的1均可通过下方数字的反转来改变;
之后处理接下来的n-2行;如果他的上方的数字为1,就将其反转;
最后处理最后一行,因为最后一行没有下一行,所以最后一行是否有1决定了是否可能;
ans记录步数
反转一次cnt++;
k & (1 << (m - 1 - j))//取k得第i位,为0为真,否则为假
\*****************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 16;
int g[N][N], t[N][N], f[N][N];
int cnt, n, m;
int x[4] = {0, 0, -1, 1};
int y[4] = { -1, 1, 0, 0};

void flip(int i, int j)//翻转
{
    ++cnt, f[i][j] = 1;//步数加1,记录翻转了哪个瓷砖
    t[i][j] = !t[i][j];//首先翻转自己
    for(int k = 0; k < 4; ++k)//向四个方向寻找,找到就翻转,这里使用了异或
        if(i + x[k] > -1 && j + y[k] > -1)
            t[i + x[k]][j + y[k]] ^= 1;
}

bool ok(int k)//对于第一行的每一种情况,判断是否能够产生最终的结果
{
    cnt = 0;//初始化步数
    memcpy(t, g, sizeof(t));//初始化临时数组,作为原始数组的副本
    for(int j = 0; j < m; ++j)//这里采用了二进制压缩,例如,j从0到3,那么1 << (m - 1 - j)的二进制就是1000,0100,0010,0001
        if(k & (1 << (m - 1 - j)))//对于k的每一个取值,如1010,找到不为0的列,因为只需要翻转1就可以了,用到了与运算
            flip(0, j);//如果某一列不为0,就翻转第一行的这个位置


    for(int i = 1; i < n; ++i)//当第一行全部翻转完了,原来为1的位置肯定是0,原来是0的位置肯定是1,这就需要第二行来解决这些为1位置,以此类推
        for(int j = 0; j < m; ++j)
            if(t[i - 1][j]) flip(i, j);//如果该列上一个位置是1,那么这个位置需要翻,否则不需要翻

    for(int j = 0; j < m; ++j)//因为每一行的1都可以由下一行搞定,但是最后一行没有下一行,所以只需要考察最后一行最后是不是全0就可以了
        if(t[n - 1][j]) return false;
    return true;
}

int main()
{
    int ans, p;
    while(~scanf("%d%d", &n, &m))
    {
        for(int i = 0; i < n; ++i)//数据输入
            for(int j = 0; j < m; ++j)
                scanf("%d", &g[i][j]);
        ans = n * m + 1, p = -1;//初始化
        for(int i = 0; i < (1 << m); ++i)//i表示一个二进制数,用来枚举第一行的各种不同翻法,如0001就是只翻最后一个
            if(ok(i) && cnt < ans) //如果找到一种可能并且所用的步数更少的话,记下这种翻法
                ans = cnt, p = i;//(有递推意味)

        memset(f, 0, sizeof(f));
        if(p >= 0)//最后找到的就是最少的翻法,模拟一遍,然后输出
        {
            ok(p);//将f中的翻转
            for(int i = 0; i < n; ++i)
                for(int j = 0; j < m; ++j)
                    printf("%d%c", f[i][j], j < m - 1 ? ' ' : '\n');
        }
        else puts("IMPOSSIBLE");
    }
    return 0;
    /*
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
    */
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值