【算法学习笔记】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;
}
时间: 2024-10-13 00:24:00

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

【算法学习笔记】42.正反DP 填充连续 SJTU OJ 1285 时晴时雨

1285. 时晴时雨 Description Taring 喜欢晴天,也喜欢雨天. Taring说:我想体验连续的K天的晴朗,去远足,去放歌:我还想再这K个晴天之后,再去体验连续的K天的云雨,去感受落雨时的轻语.这是令Taring最开心的事情了.其它的时间,Taring会在机房默默的编写着代码. 当然,Taring不想在这连续的K个晴天和连续的K个雨天里被机房的事务打扰或者被自然天气的变化中断.也就是说,这K个晴天和K个雨天必须是连续的,但是他们之间并不需要时间连续.显然的,Taring如果在感

【算法学习笔记】62.状态压缩 DP SJTU OJ 1088 邮递员小F

状态压缩,当我们的状态太多时可以考虑用bit来存储,用二进制来表示集合,用&来取交集,用^来异或. DP过程很简单,遍历所有情况取最短路径就行,因为最短哈密顿回路本身就是一个NPC问题,效率不高. #include <vector> #include <iostream> using namespace std; //最短哈密顿回路问题 NP完全问题... int map[16][16]={0}; int n=0; const int INF=768000;//3000*1

【算法学习笔记】67.状态压缩 DP SJTU OJ 1383 畅畅的牙签袋

思想来自:http://blog.pureisle.net/archives/475.html 主要思想是用1和0来表示是否被填,然后根据两行之间的状态关系来构建DP方程. 1.首先初始化第一行 计算第一行可以被横着填的方案数.此时cnt是1 所以其实合法的dp[1][j]都是1 2.然后开始构建第二行至最后一行 构建每行时,枚举上一行的可行状态,cnt += 达到该状态的方法数,从而计算dp值. 对上一行的该状态进行取反操作,得到上一行是0的位置,把它们变成1,模拟竖着填. 然后和全集按位与操

【算法学习笔记】40.树状数组 动态规划 SJTU OJ 1289 扑克牌分组

Description cxt的扑克牌越来越先进了,这回牌面的点数还可以是负数, 这回cxt准备给扑克牌分组,他打算将所有的牌分成若干个堆,每堆的牌面总和和都要大于零.由于扑克牌是按顺序排列的,所以一堆牌在原牌堆里面必须是连续的.请帮助cxt计算一下,存在多少种不同的分牌的方案.由于答案可能很大,只要输出答案除以1,000,000,009的余数即可. Input Format 第一行:单个整数:N,1 ≤ N ≤ 10^6 第二行到N + 1行:在第i + 1行有一个整数:Ai, 表示第i张牌牌

【算法学习笔记】55.DFS 记忆化搜索 SJTU OJ 1063 小M爱滑雪

Description 小M超级喜欢滑雪~~ 滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当小M滑到坡底,便不得不再次走上坡或者等待升降机来载你.小M想知道滑雪场中最长底的滑坡.滑雪场由一个二维数组给出.数组的每个数字代表点距离水平面的相对距离.下面是一个例子 1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9 小M可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小.在上面的例子中,一条可滑行的

【算法学习笔记】44. 并查集补充 SJTU OJ 3015 露子的星空

[题目描述] 已经深夜了,露子仍然在公园里仰望星空.你走近后,她对你说:“呜—,你看夜空中的星星.它们本来都是孤独地毫无联系,但人们赋予了它们各种地联想,在它们之间连上了线,便形成了夜空中无数的星座.”你回答:“是啊.为什么我们不自己创造一个美丽的星空呢?” 假设夜空中一共有n颗星星,它们初始时都没有连线.接下来,露子会给你m条指令.一共有以下三种指令: 1.在某两颗星星间连线.(可能会重复连接两颗星星) 2.询问你当前夜空中一共有多少个星座. 3.某两颗星星当前是否属于同一个星座. 其中星座是

【算法学习笔记】35.高精度 竖式乘法 SJTU OJ 1274

Description 输入a,b 输出a*b的竖式乘法,格式见样例. Sample Input1 11 9 Sample Output1 11 9 -- 99 Sample Input2 10 10 Sample Output2 10 10 --- 100 Sample Input3 101 101 Sample Output3 101 101 ----- 101 101 ----- 10201 Sample Input4 10086 2 Sample Output4 2 10086 ----

【算法学习笔记】39.字符串处理 单词分割 SJTU OJ 1302 缩进格式

1302. 缩进格式 Description 小z想和小y愉快的玩耍,但是小y在写程序.程序写好了,但是小y怎么调试也过不了.小z帮小y看了一下就不想看了,因为小y虽然是萌妹子,但是她的程序缩进实在是不忍直视.于是小z决定帮她纠正. 程序里的每一行语句分为单词和空格,ASCII码从33到126的一段连续字符串是单词,而单词之间由ASCII码为32的空格分开.小z的缩进方法具体来说是这样:对于每一行的第 i 个单词,它的第一个字符的位置不能小于其它每一行的第 1 至第 i−1 个单词,且每个单词的

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前