• 【算法学习笔记】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;
    }
  • 相关阅读:
    多级导航Menu的CSS
    Centos7在线安装PostgreSQL和PostGIS
    PostGis 根据经纬度查询两点之间距离
    在PowerDesigner中表显示中添加Code的显示
    Tomcat部署Geoserver
    PostGIS之路AddGeometryColumn函数添加一个几何类型字段
    怎样把多个excel文件合并成一个
    Error:java: 无效的目标发行版: 11
    PowerDesigner导出Excel
    GeoServer发布高清电子地图
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1391.html
Copyright © 2020-2023  润新知