POJ 2411 Mondriaan's Dream (状压DP)
Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 9135 | Accepted: 5280 |
Description
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
Output
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; }