[ACM_动态规划] 轮廓线动态规划——铺放骨牌(状态压缩1)
Description
Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series' (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways.
Expert as he was in this material, he saw at a glance that he'll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won't turn into a nightmare!
Input Specification
The input file contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.
Output Specification
For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.
Sample Input
1 2 1 3 1 4 2 2 2 3 2 4 2 11 4 11 0 0 |
Sample Output
1 0 1 2 3 5 144 51205 |
Input
The input file contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.
Output
For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.
Sample Input
1 2 1 3 1 4 2 2 2 3 2 4 2 11 4 11 0 0
Sample Output
1 0 1 2 3 5 144 51205
今天集训做这题用怎样的dp都会跪,然后我就自己模拟,还是跪......最后前辈SD指导,原来这是典型的状态压缩问题(所谓状态压缩也弄不懂啥专业术语,就知道针对这题采用的是二进制策略将d[2][][][][][]...后面的m维数组映射为一个2进制的m位,从而用一个d[2][1<<m]的二维数组就搞定啦),下面是对这题我的理解....我是从一点不懂,然后从多段图路径看到轮廓线动态压缩,然后又根据图研究本题的构造方法,最后又理解2进制操作,感觉做ACM的题目好充实,哈哈,加油!!!
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 int n,m,cur; 6 7 const int maxn=15; 8 long long d[2][1<<maxn]; 9 10 int main(){ 11 while(scanf("%d%d",&n,&m)==2){ 12 if(m==0 && n==0)return 0; 13 if(n<m)swap(n,m); 14 memset(d,0,sizeof(d)); 15 cur=0; 16 d[0][(1<<m)-1]=1;//所有d[0][j]初始化为1 17 for(int i=0;i<n;i++) 18 for(int j=0;j<m;j++){//从小到大枚举每一个阶段(共m*n阶段) 19 cur^=1; 20 memset(d[cur],0,sizeof(d[cur])); 21 for(int k=0;k<(1<<m);k++){//枚举上个阶段的状态(0-2*m-1) 22 if(k&(1<<m-1))d[cur][(k<<1)^(1<<m)]+=d[1-cur][k];//不放 23 if(i && !(k&(1<<m-1)))d[cur][(k<<1)^1]+=d[1-cur][k];//插上 24 if(j && (k&(1<<m-1)) && !(k&1))d[cur][(k<<1)^(1<<m)|(1<<1)^1]+=d[1-cur][k];//插左 25 } 26 } 27 printf("%lld\n",d[cur][(1<<m)-1]); 28 } 29 return 0; 30 } 31 /*《明明很爱你——品冠、梁静茹,伤感唯美》《红日——杨克勤,热血,积极》《如果你也听说——张惠妹,爱情,伤感》 32 ^_^!状态压缩——轮廓线动态规划 33 ^_^!共同特点:在一个比较"窄"(行数少或者列数少)的棋盘上进行复杂操作。如过采用传统方法(以整行或者整列为状态)进行规划, 34 将无法进行状态转移,因此只能把参差不齐的轮廓线也作为状态转移的一部分。 35 ^_^!方法:要用到递推关系里的多段图路径问题:从左到右有n列节点,每列称为"一个阶段",每个阶段的结点只能向下一阶段连有向 36 边(每个结点出发可以连多条边),求从阶段1到阶段n每个结点的路径条数(起点可以任意,只要是从阶段1开始的即可) 37 >> 设d[i][j]为从阶段1到结点(i,j)的路径条数,则伪代码为(已用滚动数组) 38 >> cur=0; 39 >> 所有d[0][j]初始化为1 40 >> 从小到大枚举每个要算的阶段{ //采用动态数组思想实现上下2层数据更新 41 >> cur^=1;//d[2][m]--->一层为d[cur][...]另一层为d[1-cur][...]两层数据轮换为cur^=1; 42 >> 所有d[cur][j]初始化为0//只能放在这,因为在d[cur]存着以前某个阶段的值 43 >> for 上个阶段每个结点j 44 >> for j的每个后继结点k 45 >> d[cur][k]+=d[1-cur][j] 46 >> } 47 >> 这里cur就是"正在计算"的那一阶段。最后d[cur]为阶段n各结点的值。不难发现,这些节点的名字和编号并不重 48 >> 要,只需要从小到大枚举这些阶段就好 49 ^_^!本题:铺放骨牌(用1x2的骨牌覆盖nxm的棋盘,有多少种方法?输入韩多组数据,每组包含2个整数m,n(m*n<=100),当m==n==0时 50 结束),对于每组数据输出总数。 51 ^_^!解题思路:1>因为题目中n*m<=100所以m、n至少有一个不超过10。为简单起见,我们规定m=<n,如果n>m就交换,来符合前面说的 52 "窄"棋盘条件; 53 2>这里按照从上到下、从左到右的顺序把棋盘分成m*n个阶段,每个阶段包含m个棋盘区域(第n行m列的1x1的方块及其后 54 的m-1个),我们用1已覆盖,0表示未被覆盖,这样每个阶段所有情况就是所有m位二进制,共2的m次方个结点; 55 3>阶段决策是:"以当前格子为右下角,要不要放骨牌以及放哪种骨牌"。答案有3种:不放骨牌、放竖骨牌、放横骨牌 56 $(i,j)在右下角竖放即插入上一层,横放是占据前一个和(i,j)位置$ 57 58 【】【】【】【】【】 59 【】【】【】【】【】 60 【】【】[k4][k3][k2] 61 [k1][k0](i,j) 62 | 63 | 64 |----------- 【】【】【】【】【】 65 | 【】【】【】【】【】 66 | 【】【】 1 [k3][k2] 67 | [k1][k0][0] //不放的情况只有当k4==1时,转移到k3k2k1k00状态 68 | 69 |----------- 【】【】【】【】【】 70 | 【】【】【】【】【】 71 | 【】【】 1 [k3][k2] 72 | [k1][k0][1] //往上放的情况只有当k4==0 && i!=最上层时,转移到k3k2k1k01 73 | 74 |----------- 【】【】【】【】【】 75 | 【】【】【】【】【】 76 | 【】【】 1 [k3][k2] 77 | [k1][1] [1] //往左放的情况只有当k4==1 && k0==0 && j不是最左层时,转移到k3k2k111 78 | 79 ^_^!算法复杂度为O(mn*2^m),最终答案为d[cur][2^m-1] (因为是从0开始编号的最后一个阶段即m*n-1,又因为该算法采用滚动 80 数组法,所以最后一个阶段数值保存在d[cur][...]里,上面说的设d[i][j]为从阶段1到结点(i,j)的路径条数,这里我们为了 81 防止开一个d[2][k4][k3][k2][k1][k0]的数组,所以将后面m维映射为一个2进制整数2^k4+2^k3+2^k2+2^k1+2^k0共 82 是0-2^m-1个结点,所以最后一个阶段最后一个结点存的就是答案) 83 ^_^!位运算实现状态转移:设旧状态为k,则: 84 A:不放,条件:二进制k从左往右第一个为1,即:k & (1<<m-1)); 85 新状态为k左移一位的结果:k<<1; 86 B:上插,条件:二进制k从左往右第一个为0 && i不为顶,即:i && !(k&(1<<m-1)) 87 新状态为k左移一位最后一位变为1:(k<<1)^1 88 C:左插,条件:二进制k最左一位为1,最右一位为0,且j不为左,即:j && (k&(1<<m-1)) && !(k&1) 89 新状态为k左移一位最后一位和倒数第二位变为1 90 */