POJ1753 暴力枚举加状态压缩(适合小白)

问题呈现

题目
Flip game is played on a rectangular 4x4 field with two-sided pieces placed on each of its 16 squares. One side of each piece is white and the other one is black and each piece is lying either it’s black or white side up. Each round you flip 3 to 5 pieces, thus changing the color of their upper side from black to white and vice versa. The pieces to be flipped are chosen every round according to the following rules:
Choose any one of the 16 pieces.
Flip the chosen piece and also all adjacent pieces to the left, to the right, to the top, and to the bottom of the chosen piece (if there are any).
在这里插入图片描述
Consider the following position as an example:

bwbw
wwww
bbwb
bwwb
Here “b” denotes pieces lying their black side up and “w” denotes pieces lying their white side up. If we choose to flip the 1st piece from the 3rd row (this choice is shown at the picture), then the field will become:

bwbw
bwww
wwwb
wwwb
The goal of the game is to flip either all pieces white side up or all pieces black side up. You are to write a program that will search for the minimum number of rounds needed to achieve this goal.

输入描述:
The input consists of 4 lines with 4 characters “w” or “b” each that denote game field position.

输出描述:
Write to the output file a single integer number - the minimum number of rounds needed to achieve the goal of the game from the given position. If the goal is initially achieved, then write 0. If it’s impossible to achieve the goal, then write the word “Impossible” (without quotes).

题意说明

首先给出一个 4 x 4 的地图,每个地图方格由 b 或 w 构成。

之后可以对每一个方格进行一次翻转操作,翻转的意思就是 将 b 和 w 互相转换。除此之外,被操作的方格和它的上下左右四个方格都要进行翻转。(如果被选中的格子在四个角上,则相当于一次翻转三个方格。若被选中的格子在四条边上,则相当于一次操作翻转四个方格。若在内部,则相当于一次翻转五个方格。)

问:操作数次后,原本的地图若能全部变成 b 或全部变成 w ,则称之成功,此时输出能成功的最小操作次数。若无论如何操作,均无法成功,则输出 “Impossible”。

思路分析

本题我们只讨论用枚举的方法来做。
由题意显然可以看出,每一个格子最多被翻转1次(若被翻转 2 次,则相当于不翻转,若被翻转 3 次,则相当于翻转一次,以此类推,多翻转只会增加操作次数),即每个格子都会被翻转 0 或 1 次

那么我们再来看,16 个格子中,每个格子都有翻和不翻两种情况,那么总共就是有 216 种情况,若使用传统方法,创建二维数组,一一枚举,再模拟过程,那。。。必定是一个“劳民伤财”的大工程。

接下来我们考虑如何优化。
首先,这道题中,每个方格只有 b 或 w 两种值,那我们便引入状态压缩的方法来考虑。

顾名思义,状态压缩就是将复杂的状态压缩成简单的形式,那到底有多简单呢?在本题中,若第一行的形式是 bwwb,那么我们可以令 b = 1 , w = 0 那么就可以将其换成 1001 的形式,这时候就有人想,这不就只是从一个 char 的二维数组变成一个 int 的二维数组了吗,no no no,那岂不是吃饱了撑的?
这种1001的形式,我们一方面可以将其使用a[4]的数组来存储四个数 1 0 0 1,同时我们也可以仅仅使用一个数 “9” 来表示1001,因为十进制中的9就是二进制中的1001啊,一个数字代表一行数字,也就将二维数组变成了一维数组,当然也就duck不必开那么长的数组喽。
同时遍历数组便变成了检查一个数的值。这个稍后便会明白。
看到这里想必大家基本就明白了。
比如给出一个图
在这里插入图片描述
我们可以用 9 0 15 15 四个数来存图。
接下来讨论操作的过程,还拿上述的那个图当作例子
若对第一行第一个数进行翻转,以二进制的形式来说,则会有
在这里插入图片描述
针对第一行第一个格子以及其右边和下方的两个格子
0 变 1,1 变 0,变为
在这里插入图片描述
变为了 5 8 15 15 四个数字,
这就是一般的操作步骤了。(稍后讨论变化方法)

接下来我们再进行优化的第二个思路,我们能否将枚举次数减少呢? 首先先分析一下题。
我们不妨这么想。
假设我们要将所有方格变成1,图如下
在这里插入图片描述
第一行已经都是1111了,那这时候想要将第二行也都变成1111,该怎么操作呢,显然我们不能直接去操作第二行的数,因为那样会破坏第一行的状态。那改变第二行的方法就只可以通过操作第二行方格正下方的第三行的方格来实现了。
具体来说,我们只需要将第三行的第 1 个,第 2 个和第 4 个方格进行翻转,便可以将第二行全部变成1。
同理对于第三行,则只需要操作第四行即可。

那么问题来了,第四行怎么变呢?那当然没办法变喽,我们在操作完之前的几行后,第四行若满足成功条件,那么就算成功,若不满足,那就是这个图它不可能变成目标图。

总结一下不难发现:如果前一行给定的话,那么后一行的操作也就确定了。

那么这么一来,是不是每个图给定了也就定了操作方式了呢?
如果你认为是,恭喜你,要 wa 了。

我们再分析,如果仅仅按照上述方式,那我们根本不需要操作第一行的数,这种方式真的对吗?
举个栗子
在这里插入图片描述
很显然,我们只需要操作第一行的第一个方格,便立马变成目标图 使得全图变成 1 。

但若我们若按照之前的思路分析,去不断地找下一行来改变上一行。对错也就很显然了。

那么我们之前那种方式的缺陷在哪里呢?

很显然,我们只考虑了操作2 3 4 行的数,并没有考虑翻转第一行的数的情况,若我们将第一行的所有方格翻转与否枚举出来,再一一按照这个方法进行操作,最后留下最小的答案,不就解决了吗?

同时这也将枚举次数由 216次变为了24次。也是一个极大的优化。

代码实现

在进行状压之后,我们将对每个数的每个位进行改动操作,以下是最简单的位操作,足够我们这次用。
在这里插入图片描述
我们可以发现,将某个数 x 翻转,即 0 变 1 ,1 变 0 ,我们只需使用 x^1即可。
以下为AC代码,可以参考。
我用了两个中间数组来方便遍历和模拟。

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f3f
int a[10];
int b[4]={1,2,4,8};
int t[10];      //中间数组
int k[10];      //中间数组
void f1(){
    for(int i=0;i<10;i++)
    t[i]=a[i];
}
void f2(){
    for(int i=0;i<10;i++)
    k[i]=t[i];
}
void fun(int *a, int i, int j){   //翻转某一个数组的某一个数字的某一位
    if(j==0){
        a[i]=a[i]^b[0]; a[i]=a[i]^b[1];
        a[i-1]=a[i-1]^b[0]; a[i+1]=a[i+1]^b[0];
    }
    else if(j==3){
        a[i]=a[i]^b[3];a[i]=a[i]^b[2];
        a[i-1]=a[i-1]^b[3]; a[i+1]=a[i+1]^b[3];
    }
    else
    {
        a[i]=a[i]^(b[j-1]+b[j]+b[j+1]);
        a[i-1]=a[i-1]^b[j];
        a[i+1]=a[i+1]^b[j];
    }
    
}
int main(){
    char c;
    int i,j,ans=inf;
    memset(a,0,sizeof(a));
    for(i=1;i<=4;i++){
        for(j=3;j>=0;j--){
            cin>>c;
            if(c=='b') a[i]=a[i]+b[j];       // b 是 1 ,w 是 0 
        }
    }
    f1();
    for(int i0=0;i0<2;i0++){    //枚举第一行所有翻转情况
        if(i0==1) fun(t,1,0);
        for(int i1=0;i1<2;i1++){
            if(i1==1) fun(t,1,1);
            for(int i2=0;i2<2;i2++){
                if(i2==1) fun(t,1,2);
                for(int i3=0;i3<2;i3++){
                    if(i3==1) 
                    fun(t,1,3);
                    f2();      //之后都是对k数组操作
                    int cnt=i0+i1+i2+i3;
                    for(i=2;i<=4;i++){     //将所有 1 变成 0 
                        for(int j=3;j>=0;j--){
                            if((k[i-1]&b[j])!=0)  {
                                fun(k,i,j),cnt++;}
                        }
                    }
                    if(k[4]==0) ans=min(ans,cnt);  //若最后一行全是0 则成功
                    f2();
                    cnt=i0+i1+i2+i3;
                    for(i=2;i<=4;i++){     //将所有 0 变成 1 
                        for(int j=3;j>=0;j--){
                            if((k[i-1]&b[j])==0)  {fun(k,i,j),cnt++;}
                        }
                    }
                    if(k[4]==15) ans=min(ans,cnt);  //若最后一行全是1 则成功
                    if(i3==1) fun(t,1,3);
                }
                if(i2==1) fun(t,1,2);
            }
            if(i1==1) fun(t,1,1);
        }
        if(i0==1) fun(t,1,0);
    }
    if(ans==inf)  //若没有符合条件的答案
    cout<<"Impossible"<<endl;
    else
    cout<<ans<<endl;
    return 0;
}


若有疑问,敬请提出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茂爱学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值