【算法学习笔记】74. 枚举 状态压缩 填充方案 SJTU OJ 1391 畅畅的牙签袋(改)

一开始想贪心,类似启发式搜索的感觉...后来觉得不行,而且很难写。

不如就枚举。可以通过0到2^W的中的每一个数的二进制形式来对应,第一行每个位置是否作为中心点放入十字格子的情况。

当此处为0时表示不放,1时表示放。

为什么只枚举第一行的所有情况就可以了呢。

因为第一行的情况确定之后,我们可以通过推理先改变第二行某些状态,然后再根据必须把第一行充满,可以确定第二排所有必须放十字块的位置。

生成该状态数之后,调用put函数,然后先影响下一行再通过结果来确定下一行。(这个算法的根基就是,处理每一行的时候要把上一行全部填满)

所以当到了最后一行被处理过之后,判断最后一行的合法性就可以了。

因为要处理很多位置...不想搞二进制很多事情了,就用bool数组来存状态了。

#include <iostream>
#include <algorithm>
using namespace std;
const int INF = 9999999;
bool map[20][20]={0};
bool cur[20][20]={0};
int H,W;

void print(){
    cout<<"--------"<<endl;
    for (int i = 0; i < H; ++i){
        for (int j=0; j < W; ++j){
            cout<<cur[i][j];
        }
        cout<<endl;
    }
    cout<<"--------"<<endl;
}

void copyMap(){
    for (int i = 0; i < H; ++i)
        for (int j=0; j < W; ++j)
            cur[i][j] = map[i][j];
}


void init(){
    cin>>H>>W;
    for (int i = 0; i < H; ++i){
        for (int j = 0; j < W; ++j){
            int l;
            cin>>l;
            map[i][j] = l%2; //偶数为0 奇数为1
        }
    }
}

//计算k的二进制中有多少个1
int getOnes(int k){
    int ans = 0;
    while(k>0){
        if((k&1)) //如果最右侧是1
            ans++;
        k = k>>1;//每次扔出最右的一位;
    }
    return ans;
}

void put(int lineId, int state){
    //枚举时我们认为一个 状态数state 每个位置的 0表示不放 1表示放 那么进行填补
    for (int i = 0; i < W ; ++i) //从第一排第0个位置 到第W-1个
    {
        if(state&(1<<i)){//表示是放的 则对两边及下方的进行填写
            if(lineId>=1)//可以不用写
                cur[lineId-1][i] = !cur[lineId-1][i];
            cur[lineId][i] = !cur[lineId][i];
            if(i>=1)
                cur[lineId][i-1] = !cur[lineId][i-1];
            cur[lineId][i+1] = !cur[lineId][i+1];
            cur[lineId+1][i] = !cur[lineId+1][i];
        }
    }
}


int build(){
    
    bool have = false;
    int res = INF;
    //首先要对第一排的放十字的所有可能情况进行枚举
    //然后根据第一行的摆放情况 可以确定第二行的摆放,依次决定第三行.....
    for (int i = 0; i < (1<<W); ++i)//一共有2^W种放的情况
    {
        //每次枚举前先进行copy 不能直接在map上进行修改
        copyMap();
        int cnt = getOnes(i);//对放的十字的个数进行记录
        put(0,i);
        //print();
        //第一排做完了 我们来根据第一排的情况 确定接下来每一排必须放十字的位置
        for (int k = 1; k < H; ++k)
        {
            int curState = 0;//生成状态数 用来进行调用put函数
            for (int j = 0; j < W; ++j)
            {
                if(cur[k-1][j])//如果上一行的这个地方是1 即是奇数 则肯定要在以这一行的这个位置为中心进行填补
                    curState += (1<<j);
            }
            cnt += getOnes(curState);
            put(k,curState);
            
        }
        //放完之后要检查最后一行是不是已经被填满 如果是的话 则说明全体都被填成偶数
        bool ok = true;
        for (int j = 0; j < W; ++j)
        {
            if(cur[H-1][j]){
                ok = false;
                break;
            }
        }
        if(ok){
            have = true;
            res = min(res,cnt);
        }
    }
    if(!have)
        res = -1;
    return res;
}


int main(int argc, char const *argv[])
{
    init();
    cout<<build()<<endl;
    return 0;
}

 

posted @ 2015-07-18 14:24  雨尘之林  阅读(450)  评论(0编辑  收藏  举报