poj-3279 poj-1753(二进制枚举)

题目链接:http://poj.org/problem?id=3279

题目大意:

  有一个m*n的棋盘(1 ≤ M ≤ 15; 1 ≤ N ≤ 15),每个格子有两面分别是0或1,每次可以对一个格子做一次翻转操作,将被操作的格子和与其相邻的周围4个格子都会进行翻转。问做少做多少次翻转可以将所有格子翻转成0,输出翻转方案(每个棋子的翻转次数)。没有方案时输出“IMPOSSIBLE”。

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

解题思路:

  首先需要明确每个格子只有两种情况,就是要么翻和要么不翻,因为翻奇数次都与翻1次,而翻偶数次等于没翻。再来看一下数据范围n和m最大都为15,范围不是很大,但是全部枚举的话应该也会超时,所以我们要找到一个方案减少枚举量,我们发现如果一行的状态已经确定了,那么它接下来的是不是都确定了。下一行都要保证上一行全为0就可以了,最后只需要判断一下最后一行是否都为0就可以了,而第一行有n个棋子,它的状态也就是有2^n种。我们可以用一个含有16位的二进制数来表示它的状态,如果第i位为1表示该格子需要翻转,如果为0,则表示不翻转,翻转完第一行,仅接着翻第2行,每行的状态由其上一行的状态所确定。这样只要判断最后一行是否可以全部为0即可,如果为0,判断是否比答案更小,如果更小,则对答案进行更新。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<cmath>
#include<list>
#include<deque>
#include<cstdlib>
#include<bitset>
#include<stack>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-6;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int n,m,mp[20][20],tmp[20][20],cnt[20][20],ans[20][20],res;
void flip(int x,int y)  //翻转,用异或符操作
{
    tmp[x][y]^=1;
    tmp[x+1][y]^=1;
    tmp[x-1][y]^=1;
    tmp[x][y+1]^=1;
    tmp[x][y-1]^=1;
}
int main()
{
    ios_base::sync_with_stdio(false); cin.tie(0);
    cin>>m>>n;
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cin>>mp[i][j];
        }
    }
    res=INF;  //初始答案为无穷大
    for(int s=0;s<(1<<n);s++)  //枚举第一行的所有状态
    {
        int tot=0;
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                tmp[i][j]=mp[i][j];
            }
        }
        for(int i=0;i<n;i++)
        {
            if(((s>>i)&1))  //如果第i+1位为1,则对其进行翻转
            {
                tot++;
                cnt[1][i+1]=1;
                flip(1,i+1);
            }
        }
        for(int i=2;i<=m;i++)  //翻转第2至m行
        {
            for(int j=1;j<=n;j++)
            {
                if(tmp[i-1][j])
                {
                    tot++;
                    cnt[i][j]=1;
                    flip(i,j);
                }
            }
        }
        int flag=1;
        for(int i=1;i<=n;i++)  //判断是否合法
        {
            if(tmp[m][i]!=0)
            {
                flag=0;
                break;
            }
        }
        if(flag&&tot<res)  //合法且比当前答案小对答案进行更新
        {
            res=tot;
            for(int i=1;i<=m;i++)
            {
                for(int j=1;j<=n;j++)
                {
                    ans[i][j]=cnt[i][j];
                }
            }
        }
    }
    if(res==INF)  //不能实现
    {
        cout<<"IMPOSSIBLE"<<endl;
        return 0;
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(j==1) cout<<ans[i][j];
            else cout<<" "<<ans[i][j];
        }
        cout<<endl;
    }
    return 0;
}

题目链接:http://poj.org/problem?id=1753

题目大意:

  给你一个4*4的棋盘,上面摆了16个棋子,每个棋子有两面,一面是黑色一面是白色,给出棋盘的初始状态,每次可以翻一个棋子,而且每次翻的时候连着四周的棋子一起翻,问最少几次操作可以使棋盘全变为白棋或者黑棋。

思路:

  首先可以用DFS做,数据范围较小,最多翻16个棋子(每个棋子要么翻要么不翻,再翻就和原来一样了),枚举翻的棋子数,然后用DFS搜索翻该棋子数的所有情况,然后进行判断是否全为黑或全为白,就可以找出答案。

  当然也可以用二进制枚举的方法,总共16个棋子,对应2^16种状态,直接暴力枚举全部枚举全部情况,保留答案的最小值,与上面那题类似。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<cmath>
#include<list>
#include<deque>
#include<cstdlib>
#include<bitset>
#include<stack>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-6;
const ll mod=1e9+7;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int mp[5][5],tmp[5][5],ans;
void flip(int x,int y)  //翻转
{
    tmp[x][y]^=1;
    tmp[x+1][y]^=1;
    tmp[x-1][y]^=1;
    tmp[x][y+1]^=1;
    tmp[x][y-1]^=1;
}
int main()
{
    ios_base::sync_with_stdio(false); cin.tie(0);
    for(int i=1;i<=4;i++)
    {
        for(int j=1;j<=4;j++)
        {
            char c;
            cin>>c;
            if(c=='w') mp[i][j]=0;
            else mp[i][j]=1;
        }
    }
    ans=17;
    for(int s=0;s<(1<<16);s++)  //枚举所有情况
    {
        int tot=0;
        for(int i=1;i<=4;i++)
        {
            for(int j=1;j<=4;j++)
                tmp[i][j]=mp[i][j];
        }
        for(int i=0;i<16;i++)
        {
            if((s>>i)&1)
            {
                int x=(i+1)/4+1;  //判断第i+1棋子的坐标,然后对其翻转
                int y=(i+1)%4;
                if(y==0){x--;y=4;}
                tot++;
                flip(x,y);
            }
        }
        int flag=1;
        for(int i=1;i<=4;i++)  //判断是否合法
        {
            for(int j=1;j<=4;j++)
            {
                if(tmp[i][j]!=tmp[1][1])
                {
                    flag=0;
                    break;
                }
            }
            if(flag==0) break;
        }
        if(flag&&tot<ans)  //合法更新答案
            ans=tot;
    }
    if(ans==17)
        cout<<"Impossible"<<endl;
    else
        cout<<ans<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zjl192628928/p/10031802.html