【算法学习笔记】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;
}
时间: 2024-08-15 17:45:26

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

学习笔记:状态压缩DP

我们知道,用DP解决一个问题的时候很重要的一环就是状态的表示,一般来说,一个数组即可保存状态.但是有这样的一些题 目,它们具有DP问题的特性,但是状态中所包含的信息过多,如果要用数组来保存状态的话需要四维以上的数组.于是,我们就需要通过状态压缩来保存状态,而 使用状态压缩来保存状态的DP就叫做状态压缩DP. 一道例题: HOJ 2662 有一个n*m的棋盘(n.m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻).求合法的方案总数.

【算法学习笔记】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

【算法学习笔记】43.动态规划 逆向思维 SJTU OJ 1012 增长率问题

1012. 增长率问题 Description 有一个数列,它是由自然数组成的,并且严格单调上升.最小的数不小于S,最大的不超过T.现在知道这个数列有一个性质:后一个数相对于前一个数的增长率总是百分比下的整数(如5相对于4的增长率是25%,25为整数:而9对7就不行了).现在问:这个数列最长可以有多长?满足最长要求的数列有多少个? Input Format 输入仅有一行,包含S和T两个数( 0<S<T≤200000 ). 30%的数据,0<S<T≤100 : 100%的数据,0&l

【算法学习笔记】60.经典动态规划 SJTU OJ 1370 赫萝的桃子

Description 赫萝最喜欢吃蜂蜜腌渍的桃子.然而她能够得到的桃子有限,因此赫萝必须精打细算.赫萝在b天内可以得到a个桃子,每天赫萝至少吃一个桃子,她想知道她在a天内有多少种吃桃子的方法.吃桃子的顺序并不重要,也就是说赫萝认为“第一天吃一个桃子第二天吃两个桃子”和“第一天吃两个桃子第二天吃一个桃子”算一种方法. Input Format 每个测试点有多组测试数据. 第一行一个数n,表示测试的数量. 接下来n行每行两个数a, b(a>b). Output Format 输出n行,每行一个数,

【算法学习笔记】87. 枚举路径 SJTU OJ 1999 二哥找宝藏

这个题只用BFS来搜索一次会很麻烦, 因为每次经过一个宝藏之后,要把所有的vis重置(因为可以重复经过同一点, 但是这样会有很多不必要的路径) 看题目的暗示 最多只有5个宝藏  我们要把所有的宝藏收集齐全, 如果确定了收集的顺序, 那么也就确定了路径 那么可以知道 A55的排列一共是120种路径 遍历起来毫无压力 我们枚举所有宝藏的全排列, 然后从起点开始走, 记录整个路径的步数, 最后取最小值即可. 这里生产全排列的方法利用了 STL的next_permutation函数 非常爽....(要引

【算法学习笔记】70.回文序列 动态规划 SJTU OJ 1066 小M家的牛们

这个题很多地方暗示了DP的路径. 我们处理时,dp[i][j]可以认为是从i坐标到j坐标的序列达到回文效果需要的最小代价,以此向外扩展,最终得到dp[0][M-1]就是结果. 我们要注意到处理dp[i][j]时,我们需要知道 dp(i+1,j-1)的结果,所以i必须降序,j必须升序,才能保证在计算dp(i,j)时,可以利用已经计算过的结果. 所以 i应该从M-2 到 0 递减 j在内层 从i+1到M-1 递增 在处理dp(i,j)时,第一要看name[i]和name[j]是否相等,如果相等的话,

【算法学习笔记】51. 区间排序问题 SJTU OJ 1360 偶像丁姐的烦恼

Description 成为LL冠军的人气偶像丁姐最近比较烦,许多商业活动找上门来.因为每次商业活动给的毛爷爷都一样,所以丁姐希望能够尽可能多的参加这些活动.然而,商业活动的起止时间并不由丁姐说了算,因此丁姐想写一个程序,求出他最多能够参加的商业活动的数量. Input Format 第一行一个数n,表示可选活动的数量. 接下n行每行两个数,表示每个活动开始时间t1_i和结束的时间t2_i. Output Format 一个数字,表示丁姐最多能够参加的活动的数量. Sample Input 10

【算法学习笔记】50.字符串处理 SJTU OJ 1361 丁姐的周末

Description 丁姐来到了神秘的M78星云,为了成为和凹凸曼一样强大的男人有朝一日回到地球拯救世界,丁姐开始了刻苦的学习.但丁姐先要知道在M78星云上一周有多少天,这样他才能知道什么时候是周末可以带妹子出去玩.他找到一个老凹凸曼,但是老凹凸曼自己记性不太好,偶尔会告诉他错误的信息. 凹凸曼会告诉丁姐如下格式的信息: Today is xxxday. Yesterday was yyyend. Tomorrow will be zzzday. 规则1: xxx/yyy/zzz为任意字符串,

【算法学习笔记】34.高精度除法 SJTU OJ 1026/1016

高精度除法, 这个和加减乘一样,我们都要从手算的角度入手.举一个例子,比如 524134 除以 123.结果是4261 第一位4的来源是 我们把 524和123对齐,然后进行循环减法,循环了4次,余32,将32134的前三位321继续和123对齐,循环减法2次,余75,把7534的前三位753和123对齐,循环减法6次,余15,将154和123对齐,只能减1次,所以结果是4 2 6 1. 把上述过程程序化 1.把A,B两个数存入char数组 0下标表示的是最高位2.把A的前lenB位和B对齐进行