蒙德里安的梦想——状态压缩dp

291. 蒙德里安的梦想 - AcWing题库

这道题想写出来真恶心啊(起码对于现在的我来说)主要用的方法就是正确选择dp的状态表达,以及使用状态压缩把每一种状态用二进制的01式子表示出来。

 

目前碰到的状态压缩就是把一种状态抽象成0与1,以01式子的形式表示该状态。

 

对于这道题,我们可以先分析每一列的状态,然后遍历就得到了整张图的状态。

摆放方块的时候,先放横着的,再放竖着的。总方案数等于只放横着的小方块的合法方案数。

状态表达:f[i,j]表示为在前i-1列状态已经满足题意,第i-1向第i列突出的状态为j的所有方案数量。

状态计算:

  既然第 i 列固定了,我们需要看 第i-2 列是怎么转移到到第 i-1列的(看最后转移过来的状态)。假设此时对应的状态是k(第i-2列到第i-1列伸出来的二进制数,比如00100),k也是一个二进制数,1表       示哪几行小方块是横着伸出来的,0表示哪几行不是横着伸出来的。它对应的方案数是 f[i−1,k]f[i−1,k] ,即前i-2列都已摆完,且从第i-2列伸到第i-1列的状态为 k 的所有方案数。

AcWing 291. 蒙德里安的梦想 - AcWing(详细题解)

 

每种正确的状态应该满足:

  1.每一列都没有连续奇数个0(这样就可以保证每一列都可以放下竖着的小方块并且不留空隙)

  2.第i-1列伸向第i列的状态以及第i-2列伸向i-1列的状态不会有矛盾的出现(不会有重叠的突起小方块)

所以我们先预处理出满足这两种条件的所有状态,然后再进行dp

 

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N=12,M=1<<N;
 4 long long f[N][M];
 5     //f[i,j]表示在i-1列已经ok,并且i-1伸到i的状态为j的方案数量 
 6 vector<int> sta[M];
 7 bool st[M];
 8 
 9 
10 int main()
11 {
12     int n,m;
13     while(~scanf("%d%d",&n,&m)&&(n||m))
14     {
15         //先预处理满足一列没有连续奇数个0的所有状态 
16         for(int i=0;i< 1<<n;i++)
17         {
18             int cnt=0,flag=1;
19             //cnt表示该状态连续0的个数,flag=1表示该状态没有连续奇数个0
20             for(int j=0;j<n;j++)    //从上往下遍历这一列 
21             {
22                 if(i>>j &1)    //在i状态中第j位是1(凸出来) 
23                 {
24                     if(cnt&1)    //有连续奇数个0
25                     {
26                         flag=0;    
27                         //该状态不满足 没有连续奇数个0 这一条件 
28                         break;    //没必要再在这个状态上浪费时间 
29                     } 
30                     cnt=0;    //有连续偶数个0,依然符合条件,继续循环判断 
31                 }
32                 else cnt++;    // i状态中第j位是0 
33             } 
34             //判断该列的最后一行的cnt是否为奇数
35             //举个例子,0001。 
36             if(cnt&1)flag=0;    
37             st[i]=flag;    //记录一下某列如果状态为i,是否符合条件 
38         }
39         
40         //再预处理i-1与i-2伸出来的部分没有矛盾的状态 
41         for(int j=0;j< 1<<n;j++)    //i列的状态 
42         {
43             sta[j].clear();
44             for(int k=0;k< 1<<n;k++)    //i-1列的状态 
45             {
46                 //(j&k)==0的意思是只有在i-1与i-2伸出来的部分不会重叠时
47                 //我们才选择这个状态
48                 //j|k的意思是 j状态与k状态合起来的i-1列到底有几个1 
49                 //因为st[]表示满足预处理1的状态,所以st[j|k]就是用来
50                 //判断是否j|k的状态满足条件1 
51                 if((j&k)==0&&(st[j|k]))
52                     sta[j].push_back(k);    //满足两个条件就记录下来 
53             }
54         }
55         
56         //正式开始dp
57         memset(f,0,sizeof f);
58         f[0][0]=1;
59         for(int i=1;i<=m;i++)
60             for(int j=0;j< (1<<n);j++)
61                 for(auto k:sta[j])    //选择满足条件的状态
62                      f[i][j]+=f[i-1][k];
63          
64         
65         printf("%lld\n",f[m][0]);
66         
67     }
68     
69     
70     
71     return 0;
72 }
View Code

 

posted @ 2022-03-29 19:01  wellerency  阅读(58)  评论(0编辑  收藏  举报