Poj_2965 The Pilots Brothers' refrigerator(状态压缩,bfs)

题意:

有一个4*4的矩阵,01填充,通过翻转把所有的0变为1,但是翻转[i,j]的同时,要把同行同列的都翻转。

思路:

最开始的时候写了裸的BFS,太过暴力所以果断TLE。看了题解才知道是状态压缩,很高兴能遇到这样经典的状态压缩题目。虽然有巧妙的解法,但是从状态压缩中学到了更多的东西。

首先有几个点要先明白:

1. 每个点最多翻转一次;(翻转2n次相当于没翻转,翻转2n+1次相当于翻转1次)

2. 每个点翻转的顺序无所谓,组合起来的结果相同;

3. unsigned short(16 bits)可以存储一个情况;

4. 最多有2^16=65536种组合情况;

5. 状态的转换可以用位运算完成;

这样我们可以用一个unsigned short型的变量来存储状态,另外用一个结构体数组来存储状态的前驱和变换次数,两个数组都只需要开到65536,这样我们再进行BFS效率就会高很多了。

代码实现:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAX = 65536;

struct Node{
    bool exist;
    int pre;
    int change;
    int times;
};

int pos;
int num;
Node que[MAX];
int dx[16] = {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4};
int dy[16] = {1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4};
unsigned short lis[MAX];
unsigned short update_pos(unsigned short cur,int type);
void check(unsigned short cur);
int main(){
    char tmp;
    int cnt = 0;
    unsigned short initial = 0;
    for(int i = 0;i < 4; i++ ){
        for( int j = 0; j < 4; j++ ){
            scanf("%c",&tmp);
            if( tmp == '-' ){
                initial |= (1<<cnt);
            }
            cnt++;
        }
        getchar();
    }
    for( int i = 0; i < MAX; i++ ){
        que[i].change = -1;
        que[i].exist = false;
        que[i].pre = -1;
        que[i].times = 0;
    }
    que[initial].exist = true;
    pos = 0;
    num = 1;
    lis[0] = initial;
    //check(initial);
    while( pos < num ){
        unsigned short cur = lis[pos];
        for( int i = 0; i < 16; i++ ){
            unsigned short rep = update_pos(cur,i);
            //check(rep);
            if( que[rep].exist == true ){
                continue;
            }
            que[rep].exist = true;
            que[rep].pre = cur;
            que[rep].change = i;
            que[rep].times = que[cur].times+1;
            lis[num] = rep;
            num++;
            //结果可以按任意顺序输出
            if( rep == 0xffff ){
                printf("%d\n",que[rep].times);
                while( que[rep].pre != -1 ){
                    printf("%d %d\n",dx[que[rep].change],dy[que[rep].change]);
                    rep = que[rep].pre;
                }
                return 0;
            }
        }
        pos++;
    }
    return 0;
}

unsigned short update_pos(unsigned short cur,int type){
    unsigned short mod;
    switch(type){
        case 0:
            mod = 0x111f;break;
        case 1:
            mod = 0x222f;break;
        case 2:
            mod = 0x444f;break;
        case 3:
            mod = 0x888f;break;
        case 4:
            mod = 0x11f1;break;
        case 5:
            mod = 0x22f2;break;
        case 6:
            mod = 0x44f4;break;
        case 7:
            mod = 0x88f8;break;
        case 8:
            mod = 0x1f11;break;
        case 9:
            mod = 0x2f22;break;
        case 10:
            mod = 0x4f44;break;
        case 11:
            mod = 0x8f88;break;
        case 12:
            mod = 0xf111;break;
        case 13:
            mod = 0xf222;break;
        case 14:
            mod = 0xf444;break;
        case 15:
            mod = 0xf888;break;
    }
    return cur^mod;
}

void check(unsigned short cur){
    for( int i = 0; i < 4; i++ ){
        for( int j = 0; j < 4; j++ ){
            printf("%d ",cur%2);
            cur /= 2;
        }
        printf("\n");
    }
    printf("\n");
}


另外的一种巧妙解法,也很好理解:

每一个+都可以通过7次翻转变为-(中间加同行同列每个格子转一次),同时同行同列的其余六个格子(变换4次)不改变,其余格子(变换2次)也不改变。那么我们可以按照这个方法操作每一个关闭的开关'+',想要得到最少就是要去掉不必要的翻转,而不必要的翻转就是那些操作了偶数次的点(因为一个点翻转了偶数次的效果和不翻转效果相同)。之后把那些统计操作次数为奇数次的点按顺序操作一次就好了(因为一个点操作奇数次和操作1次的效果相同,且顺序任意)。

代码实现:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

using namespace std;

int res;
int cnt[16];
int main(){
    res = 0;
    char tmp;
    memset(cnt,0,sizeof(cnt));
    for( int i = 0; i < 16; i++ ){
        cin>>tmp;
        if( tmp == '+' ){
            int row = i/4;
            int column = i%4;
            cnt[row*4]++;
            cnt[row*4+1]++;
            cnt[row*4+2]++;
            cnt[row*4+3]++;
            cnt[column]++;
            cnt[column+4]++;
            cnt[column+8]++;
            cnt[column+12]++;
            cnt[row*4+column]--;
        }
    }
    for( int i = 0; i < 16; i++ ){
        if( cnt[i]%2 == 1 ){
            res++;
        }
    }
    printf("%d\n",res);
    for( int i = 0; i < 16; i++ ){
        if( cnt[i]%2 == 1 ){
            printf("%d %d\n",i/4+1,i%4+1);
        }
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值