• 【算法学习笔记】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,模拟竖着填。

      然后和全集按位与操作,然后开始构建这一行。

    3.注意 要尽量让列的数目变小 因为循环的次数里  H*2^W 让W小比较好。

    看着多是因为注释比较多,其实这个方法的代码量极少。

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    
    
    int W,H;
    unsigned long long cnt=1;//用于每次的叠加
    unsigned long long dp[30][1<<12];
    //dp[i][j] 表示达到第i行的 j状态有多少种方法
    //最终答案就达到最后一行全部填满的方法 所以就是 dp[H][(1<<W)-1]
    
    void build_line(int line, int from ,int cur ){
        //line是当前构建的行的行号
        //from是正在处理状态
        //cur是当前处理的位置
        if(cur == W){//一行处理结束了 注意: cur是从0开始到W-1的
            dp[line][from] += cnt;
            return;
        }
        //保持状态不变 即不去试图填了 向后构建 
        build_line(line,from,cur+1);
       //在from的状态基础上继续试图填两个连续横着的空
        //判断from里是否可以横着填两个
        if(cur <= W-2 //要保证可以填下 W-2 和 W-1这两个空
            and !( from & (1<<cur)     )  //保证正在处理的位置和它的下一个位置没有被填
            and !( from & (1<<(cur+1)) )  //
        ){
            //横着连续填两个空
            int next = from | (1<<cur) | (1<<(cur+1)); 
            build_line(line,next,cur+2);//继续构建
        }
       return;
    }
    
    
    int main(int argc, char const *argv[])
    {
           while(1){
               cin>>W>>H;
               if(W==0 and H==0)
                   break;
               if(W > H){//让列数尽量小 优化效率因为W要作为指数
                   int t; t = W; W = H; H = t;
               }
               //如果是奇数 直接输出0 然后判断下一状态
               if( W*H % 2 ==1){
                   cout<<0<<endl;
                   continue;
               } 
               //清空数组
               memset(dp,0,sizeof(dp));
               cnt = 1;//重置为1 
               build_line(1,0,0);//构建第一行 
    
               //从第二行开始枚举上一行的所有状态 构建本行
               for (int i = 2; i <= H; ++i) //i是行
               {
                   for (int j = 0; j < (1<<W); ++j)//j是枚举出来的i-1行的状态数
                   {
                       if(dp[i-1][j] > 0){//如果可以达到上一行的j状态
                           cnt = dp[i-1][j];
                       }else//无效状态 直接返回
                           continue;
                       //取反j 再进行按位与运算 从而求得 可以竖着填的情况
                       build_line(i,(~j) & ((1<<W)-1) ,0);
                   }
               }
             //输出结果
               cout<<dp[H][((1<<W)-1)]<<endl;
           }
        return 0;
    }
  • 相关阅读:
    内联元素和块元素
    inline-block
    overflow:hidden
    鼠标点到某个位置出现手势的效果。
    对于清浮动问题,终极。
    将所需要的图标排成一列组成一张图片,方便管理。li的妙用
    异步错误处理 -- 时机
    错误传播 --try{}catch(e){console.log(e)}
    js
    jquery源码学习-2-选择器
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1383.html
Copyright © 2020-2023  润新知