POJ 3279 Fliptile (二进制+搜索)

【题目链接】click here~~

【题目大意】:

农夫约翰知道聪明的牛产奶多。于是为了提高牛的智商他准备了如下游戏。有一个M×N 的格子,每个格子可以翻转正反面,它们一面是黑色,另一面是白色。黑色的格子翻转后就是白色,白色的格子翻转过来则是黑色。游戏要做的就是把所有的格子都翻转成白色。不过因为牛蹄很大,所以每次翻转一个格子时,与它上下左右相邻接的格子也会被翻转。因为翻格子太麻烦了,所以牛都想通过尽可能少的次数把所有格子都翻成白色。现在给定了每个格子的颜色,请求出用最小步数完成时每个格子翻转的次数。最小步数的解有多个时,输出字典序最小的一组。解不存在的话,则输出IMPOSSIBLE。

【解题思路】:

首先,同一个格子翻转两次的话就会恢复原状,所以多次翻转是多余的。此外,翻转的格子的集合相同的话,其次序是无关紧要的。因此,总共有2NM种翻转的方法。不过这个解空间太大了,我们需要想出更有效的办法。让我们再回顾一下前面的问题。在那道题中,让最左端的牛反转的方法只有1种,于是用直接判断的方法确定就可以了。同样的方法在这里还行得通吗?不妨先看看最左上角的格子。在这里,除了翻转(1,1)之外,翻转(1,2)和(2,1)也可以把这个格子翻转,所以像之前那样直接确定的办法行不通。于是不妨先指定好最上面一行的翻转方法。此时能够翻转(1,1)的只剩下(2,1)了,所以可以直接判断(2,1)是否需要翻转。类似地(2,1)~(2,N)都能这样判断,如此反复下去就可以确定所有格子的翻转方法。最后(M,1)~(M,N)如果并非全为白色,就意味着不存在可行的操作方法。像这样,先确定第一行的翻转方式,然后可以很容易判断这样是否存在解以及解的最小步数是多
少,这样将第一行的所有翻转方式都尝试一次就能求出整个问题的最小步数。这个算法中最上面一行的翻转方式共有2N种,复杂度为O(MN2N)。
代码:

// C
#ifndef _GLIBCXX_NO_ASSERT
#include <cassert>
#endif

#include <cctype>
#include <cerrno>
#include <cfloat>
#include <ciso646>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>

#if __cplusplus >= 201103L
#include <ccomplex>
#include <cfenv>
#include <cinttypes>
#include <cstdalign>
#include <cstdbool>
#include <cstdint>
#include <ctgmath>
#include <cwchar>
#include <cwctype>
#endif

// C++
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>

#if __cplusplus >= 201103L
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <forward_list>
#include <future>
#include <initializer_list>
#include <mutex>
#include <random>
#include <ratio>
#include <regex>
#include <scoped_allocator>
#include <system_error>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#endif

using namespace std;

#define rep(i,j,k) for(int i=(int)j;i<(int)k;++i)
#define per(i,j,k) for(int i=(int)j;i>(int)k;--i)
#define lowbit(a) a&-a
#define Max(a,b) a>b?a:b
#define Min(a,b) a>b?b:a
#define mem(a,b) memset(a,b,sizeof(a))

typedef long long LL;
typedef unsigned long long LLU;
typedef double db;
const int N=16;
const int inf=0x3f3f3f3f;
int n,m,t,ans,res,cnt,tmp;

char str[N];
bool vis[N];
int mat[N][N];///状态之前
int flip[N][N];///状态中间
int Res[N][N];///状态之后

int dir4[4][2]= {{1,0},{0,1},{-1,0},{0,-1}};
int dir8[8][2]= {{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}};
int movv[5][2]= {{1,0},{0,1},{0,0},{-1,0},{0,-1}};

using namespace std;

inline LL read()
{
    int c=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();}
    return c*f;
}

bool ok(int dx,int dy)
{
    if(dx>=0&&dx<n&&dy>=0&&dy<m) return true;
    return false;
}
void getMat()///输入
{
    for(int i=0; i<n; ++i)
        for(int j=0; j<m; ++j)
            scanf("%d",&mat[i][j]);
}

int getChange(int x,int y)///判断x,y的颜色
{
    int c=mat[x][y];
    for(int i=0; i<5; ++i)
    {
        int dx=x+movv[i][0];
        int dy=y+movv[i][1];
        if(ok(dx,dy))c+=flip[dx][dy];
    }
    return c&1;///奇数为1,偶数为0
}
int calc()
{
    ///求出第二行开始的翻转方法
    for(int i=1; i<n; ++i){
        for(int j=0; j<m; ++j){
            if(getChange(i-1,j)!=0)///是白色翻转
                flip[i][j]=1;
        }
    }
    ///判断最后一行是否全白
    for(int j=0; j<m; ++j){
        if(getChange(n-1,j)!=0)
            return -1;
    }
    int sum=0;///统计翻转次数
    for(int i=0; i<n; ++i){
        for(int j=0; j<m; ++j){
            sum+=flip[i][j];
        }
    }
    return sum;
}
void solve()
{
    int res=inf;
    for(int i=0; i< (1<<m); ++i){///枚举第一行的情况
        mem(flip,0);
        for(int j=0; j<m; j++){
            flip[0][m-j-1] = i >> j & 1;
        }
        int now=calc();
        if(now>=0&&now<res){
            res=now;
            memcpy(Res,flip,sizeof(flip));
        }
    }
    if(res!=inf){
        for(int i=0; i<n; ++i){
            for(int j=0; j<m; ++j){
                printf("%d%c",Res[i][j],j==m-1?'\n':' ');
            }
        }
    }
    else puts("IMPOSSIBLE");
}
int main()
{
    while(cin>>n>>m){
        getMat();
        solve();
    }
    return 0;
}
</span>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值