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
The input 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
(一句话题意)
本题的数据范围很小,nm均为11,这提示我们可以用状压。我们可以预处理出所有横放骨牌的状态,即表示为一个二进制串,如果为1,则证明这里有骨牌,为0,则证明这里没有骨牌。
1 bool check(int x) 2 { 3 bool ok=1; 4 for(int i=0;i<n;i++) 5 { 6 if(x&(1<<i)) ok=ok^1; 7 //当前位为1,记下为1的个数,最终必须两两相邻且个数为偶数 8 if((!(x&(1<<i)))&&!ok) return 0; 9 } 10 if(ok) return 1; 11 else return 0; 12 }
我们考虑设计一个状态,f[i][j]表示当前在第i行,当前行状态为j的方案数,我们的转移目标是f[m][(1<<n)-1](即在最后一行,每个地方都被填满)
则可以写出转移方程:f[i][j]=+f[i-1][bin[k]^j^fAKe](k枚举上一行的状态,满足条件k是j的子集,也就是能转移到当前状态的上一状态)两次异或是在求能转移到当前状态的上一状态。按学长的话说就是:那么我们对于每个状态,如果我们知道了它哪些是横放的,那么我们也就可以推出它上一行的状态,因为当前行必须要把上一行补满。
注意细节:开long long(WA了好几次)
code
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 5 using namespace std; 6 typedef long long ll; 7 8 int n,m,cnt; 9 int bin[3000]; 10 ll f[12][2050]; 11 12 bool check(int x) 13 { 14 bool ok=1; 15 for(int i=0;i<n;i++) 16 { 17 if(x&(1<<i)) ok=ok^1; 18 if((!(x&(1<<i)))&&!ok) return 0; 19 } 20 if(ok) return 1; 21 else return 0; 22 } 23 24 void init() 25 { 26 memset(f,0,sizeof(f)); 27 memset(bin,0,sizeof(bin)); 28 cnt=0; 29 } 30 31 int main() 32 { 33 34 while(scanf("%d%d",&n,&m)) 35 { 36 if(n==0) break; 37 int fAKe=(1<<n)-1; 38 for(int i=0;i<=fAKe;i++) 39 if(check(i)) bin[++cnt]=i; 40 for(int i=1;i<=cnt;i++) f[1][bin[i]]=1; 41 for(int i=2;i<=m;i++) 42 for(int j=0;j<=fAKe;j++) 43 for(int k=1;k<=cnt;k++) 44 if((bin[k]|j)==j) f[i][j]+=f[i-1][bin[k]^j^fAKe]; 45 printf("%lld\n",f[m][fAKe]); 46 init(); 47 } 48 return 0; 49 }
注:我们这里说的子集概念,举个例子:
{1,3,4}为一集合,则二进制第1位或第三位或第4位上为1的所有数称为它的子集。由于或运算满足“有一个1即为1”,则我们通过或运算可以来检查是否存在子集关系。
POJ 3411 Mondriaan's Dream 【状压Dp】 By cellur925
原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9445523.html