POJ ~ 3279 ~ Fliptile(二进制枚举+模拟递推)

题意:给你一个n*m的黑白棋盘,问能否通过操作使得棋盘全变为白色。如果有求一种操作次数最少的方法(相同时要求字典序最小),没有输出"IMPOSSIBLE"。操作为:将某一块和它上下左右全部翻转。

思路:由于我们每次反转都会影响到上面的棋子,我们想全变为白色,我们需要从第二行到第n行每一行把它的上一行的黑色块翻转为白色,如果最后一行把上一行的翻转为白色之后最后一行还有黑色块,证明这种方法不能将棋盘全变白。我们可以发现只要第一行的状态一定,下面的翻法也就一定了。所以我们只需要枚举第一行的所有翻法就可以,我们可以通过用枚举二进制数字的方法来表示翻法。例如:n=5,m=5。00101表示第三块和最后一块进行翻转,此时二进制数最大为11111即(1<<5)-1。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF = 1e9 + 5;
const int MAXN = 20;
int n, m;
bool MAP[MAXN][MAXN], ans[MAXN][MAXN];//地图和答案
bool t[MAXN][MAXN], vis[MAXN][MAXN];//临时图和临时答案
void FLIPS(int x, int y)//翻
{
    t[x][y] = !t[x][y];//自己
    t[x - 1][y] = !t[x - 1][y];//上
    t[x + 1][y] = !t[x + 1][y];//下
    t[x][y - 1] = !t[x][y - 1];//左
    t[x][y + 1] = !t[x][y + 1];//右
}
bool judge()//检查最后一行
{
    for(int i = 1; i <= m; i++)
    {
        if(t[n][i] == 1) return false;//有黑色的
    }
    return true;
}
int work(int cnt)//2-n行翻转
{
    for(int i = 2; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(t[i - 1][j] == 1)//上一行的j位置为黑,那么就要翻转这一块
            {
                vis[i][j] = true;
                FLIPS(i, j);
                cnt++;
            }
        }
    }
    if(judge()) return cnt;//最后一行全为白色
    return INF;
}
int main()
{
    while(cin >> n >> m)
    {
        //输入
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                cin >> MAP[i][j];
            }
        }
        //求解
        int MIN = INF;//存储步数最小值
        for(int i = 0; i < (1 << m); i++)//枚举第一行踩的方法
        {
            memcpy(t, MAP, sizeof(t));//MAP赋给一个临时图t
            memset(vis, 0, sizeof(vis));//vis表示踩哪
            int cnt = 0;//统计这种方案下需要操作翻转的次数
            for(int j = 0; j < m; j++)//对第一行进行操作
            {
                if(i&(1 << j))
                {
                    vis[1][j + 1] = true;
                    FLIPS(1, j + 1);
                    cnt++;
                }
            }
            //work推出下面2-n行的翻转情况
            if(work(cnt) < MIN)//更新步数和答案
            {
                MIN = cnt;
                memcpy(ans, vis, sizeof(vis));
            }
        }
        //cout << endl;
        if(MIN == INF)//没答案
        {
            printf("IMPOSSIBLE\n");continue;
        }
        //输出答案
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                cout << ans[i][j] << " ";
            }
            cout << endl;
        }
    }
	return 0;
}
/*
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值