POJ 2411 Mondriaan's Dream (状压DP)

Mondriaan's Dream
Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 9135   Accepted: 5280

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

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

Source

 
 
 

  1, 因为小的矩形为1*2或2*1,那么由这两种构成的大矩形的面积必然为偶数,所以我们可以知道若是n*m为奇数,那么答案必然为0.
  
  2, 那么我们如今来解析小矩形的摆放景象: ①横着放   ②和上方一行一路竖着放  ③和下面一路竖着放。

      a,:   1   1   来默示横放了一个矩形;


      b,:   0  

              1     来默示和上方一行一路竖着放了一个矩形;

      c,:   1  

              0     默示和下面一行一路放了一个矩形;

      总结 :至于为什么这么放呢,因为如许 才干够区分出怎么放的,你可以动手画画,在所有画出来的图中,只有以上三种矩形的摆放形式!                                                       
  
  3, 那么按照矩形的摆放的形式,我们知道当前行只会受到上方一行的影响。那么我们用dp[i][j]默示前i-1行已经摆放好的景象下第i行的状况为j时的摆放数,那么我们知道dp[i][j] += dp[i-1][k] (k是i-1所有满足的状况)。如今我们就是去解析每一行的状况了,我们利用二进制的思惟,因为有m 列,那么每一行的最多的状况数为(1<<m)-1个,所以我们就可以经由过程列举去求出每一行的满足的前提,最后的答案就是dp[n-1][(1<<m)-1](n是从0开始的).
  
  4, 如今推敲第i行j列这个dp[i][j]式子:(有点类似枚举的意思)

       ① 若是是横放矩形,那么就是这一行和上方一行的i和j都是1  ;(可能你会有疑问,为啥不可能是竖着摆放,那就再好好想想那三种摆放形式,当前状况已是横放,所以那两种竖放的小矩形形式不可能再出现了,仔细详细)

       ② 若是是竖放有两种景象:若是是和上一行竖放那么这一行的j列为1,上一行的j列为0 ; 若是是和下一行竖放的,那么就是这一行的j列为0,下一行必然为要为1才能满足要求;
  
  5, 初始化第一行,这特殊的一行,因为第一行是没有前面一行的。所以必须先预处理出第一行满足的所有的状况,然后列举 1 ~ n-1 行。
  
  6, 这里求出每一行的满足的状况时有两种思路:①是经由过程dfs的思路,直接搜刮出所有的可能满足的状况  ②直接暴力列举1-(1<<m)-1所有的状况。
     然则效力上方dfs是绝对的快(还有因为数据很小开个数组打表0ms妥妥的啊!)

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

int n,m;
long long dp[12][(1<<12)];

void init(int state,int pos){   //预处理第一行的所有状态
    if(pos==m){
        dp[0][state]=1;
        return ;
    }
    if(pos+1<=m)    /*当前竖放那么就是0(state<<1,下一位就变成0),那么直接向下一个地位*/
        init(state<<1,pos+1);
    if(pos+2<=m)    /*当前地位横放那么就是两个1(state<<2|3把后两位变成1),所以向后两个地位*/
        init(state<<2|3,pos+2);
}

void DFS(int i,int curstate,int prestate,int pos){  /*dfs出每一行的可满足的状况*/
    if(pos==m){
        dp[i][curstate]+=dp[i-1][prestate]; /*当前行加上上一行的规划数*/
        return ;
    }
    if(pos+1<=m){   /*若是可以竖放,推敲两种景象*/
        DFS(i,curstate<<1|1,prestate<<1,pos+1); /*这一行和上一行竖放,那么这一行动1,上一行动0*/
        DFS(i,curstate<<1,prestate<<1|1,pos+1); /*上一行(相当于这一行)和这一行(相当于下一行)竖放,那么上一行动1,这一行动0*/
    }
    if(pos+2<=m)    /*若是可以横放*/
        DFS(i,curstate<<2|3,prestate<<2|3,pos+2);
}

int main(){

    //freopen("input.txt","r",stdin);

    while(~scanf("%d%d",&n,&m)){
        if(n==0 && m==0)
            break;
        if(n*m%2==1){
            printf("0\n");
            continue;
        }
        memset(dp,0,sizeof(dp));
        init(0,0);
        for(int i=1;i<n;i++)    /*从1开端列举*/
            DFS(i,0,0,0);
        printf("%I64d\n",dp[n-1][(1<<m)-1]);
    }
    return 0;
}

下面这个可以帮助理解,但是TLE了

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int n , m;
long long dp[12][1<<12];

/*对第一行的预处理惩罚*/
void init(int state , int pos){
     if(pos >= m){
        dp[0][state] = 1;
        return;
     }
     if(pos < m)
        init(state<<1 , pos+1);
     if(pos + 1 < m)
        init(state<<2|3 , pos+2);
}

/*断定当前两种状况是否满足*/
bool judge(int stateX , int stateY){
     int x , y , pos;
     pos = 0;
     while(pos < m){
          x = stateX & (1<<(m-pos-1));/*当前行的这个地位的值*/
          y = stateY & (1<<(m-pos-1));/*上一行的这个地位的值*/
          if(x){ /*若是当前这一行动1,上一行0就满足,若是是1断定下一个地位*/
            if(y){/*若是上一行也为1,断定下一个地位*/
              pos++;
              x = stateX & (1<<(m-pos-1));
              y = stateY & (1<<(m-pos-1));
              if(!x || !y)/*有一个为0,就不满足*/
                return false;
            }
            pos++;
          }
          else{/*当前行动0,上一行必然要为1才满足*/
            if(!y)
              return false;
            pos++;
          }
     }
     return 1;
}

void solve(){
     int i , j , k;
     memset(dp , 0 , sizeof(dp));
     init(0 , 0);
     for(i = 1; i < n ; i++){/*列举每一行*/
        for(j = 0 ; j < 1<<m ; j++){/*列举当前行的状况*/
              for(k = 0 ; k < 1<<m ; k++){/*列举上一行的状况*/
                  if(judge(j , k))/*若是满足前提*/
                     dp[i][j] += dp[i-1][k];/*加上*/
             }
         }
     }
     cout<<dp[n-1][(1<<m)-1]<<endl;
}

int main(){
    //freopen("input.txt","r",stdin);
    while(scanf("%d%d" , &n , &m)){
         if(!n && !m)
            break;
         if((n*m)%2)
            printf("0\n");
         else
            solve();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/jackge/p/3097205.html