poj2411之用1*2砖块铺满n*m-状态压缩dp

Mondriaan's Dream
Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 10549   Accepted: 6135

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

poj2411之用1*2砖块铺满n*m-状态压缩dp_第1张图片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
未优化写法:
/*分析:用1*2的砖去恰好铺满n*m的空间,对于第k行第j列,有3种情况将该点铺满
1:由第k-1行第j列砖竖着铺将第k行第j列铺满
2:由第k行第j列被横铺砖铺满
3:第k行第j列砖竖着铺将该点铺满
所以对于每一列的情况其实有两种(1,0)表示该点铺砖还是不铺
而对于每一列必须到达的状态只有一种,就是被铺满(1)
但是由上述3种情况将铺满方式分成两种:
0和1表示被k-1行j列竖铺铺满和在k-1行被横铺铺满 
对于每一行列举每一种到达的状态j,dp[j]表示到达该状态有多少种情况
分析对于第k-1行状态j:10000111
需要到达第k行状态i:  01111011
如果需要到达第k行j列状态是0,则必须第k-1行该点状态不能是0,否则一定是连续两列竖放冲突
所以到达第k-1行该点只能是1,也就是说i|j一定每一位是1,也可以一步步判断是否满足第k行j列是0第k-1行j列是1 
如果需要到达第k行状态j列是1,则假如第k-1行该点是0,则该点状态可以到达,继续判断j+1列
假如第k-1行该点是1,则第k行j列的1一定是横铺到达的,所以k行第j+1列一定也被铺满为1
从而第k-1行j+1列一定不能竖铺,必须被横铺铺满,所以也是1.
于是综合的第k行j列和第k-1行j列的关系(每一行每一列都表示到达的状态)
1:下面这种情况从第j列继续去判断j+1列 
  1
  0
2:下面这种情况从第j列继续去判断j+1列 
  0
  1
3:下面这种情况从第j列判断第j+1列是否全是1,然后继续判断第j+2列
  1
  1 
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <map>
#include <cmath>
#include <iomanip>
#define INF 99999999
typedef long long LL;
using namespace std;

const int MAX=(1<<11)+10;
int n,m;
LL temp[MAX],dp[MAX],bin[15];
bool mark[MAX];

bool check(int i){
	while(i){
		if(i&1){
			i>>=1;
			if(!(i&1))return false;//第j列是1则第j+1列必须是1 
			i>>=1;//继续判断下一列 
		}else i>>=1;//继续判断下一列 
	}
	return true;
}

void Init(){
	memset(mark,false,sizeof mark);
	memset(temp,0,sizeof temp);
	for(int i=0;i<bin[m];++i){//初始化第一行和可以到达什么状态 
		if(check(i))temp[i]=1,mark[i]=true;
	}
}

void DP(){
	for(int k=2;k<=n;++k){
		for(int i=0;i<bin[m];++i)dp[i]=0;
		for(int i=0;i<bin[m];++i){
			for(int j=0;j<bin[m];++j){
				if((i|j) != bin[m]-1)continue;//每一位或之后必须每一位是1(综合前面3种情况和分析可知)
				if(!mark[i&j])continue;//由初始化和前面分析三种情况分析可知i&j必须得到和初始化可以到达的状态一样才行
				dp[i]+=temp[j];//i可以从j到达,则增加j的方案数 
			}
		}
		for(int i=0;i<bin[m];++i)temp[i]=dp[i];
	}
}

int main(){
	bin[0]=1;
	for(int i=1;i<12;++i)bin[i]=2*bin[i-1];
	while(~scanf("%d%d",&n,&m),n+m){
		if(n<m)swap(n,m);//始终保持m<n,提高效率 
		Init();
		DP();
		printf("%lld\n",temp[bin[m]-1]);//输出最后一行到达时的状态必须全部是1 
	}
	return 0;
}
优化后的代码:大大提高效率( 不用搜索也可以采用邻接表记录能到达状态i的状态j,就不需要枚举所有状态j)
/*优化:不去盲目的列举所有状态i和j然后判断状态j能否到达i,这样效率很低,因为能到达i的状态j很少
因此对于每种状态i,由i区搜索能到达i的状态j,大大提高效率
有298ms->32ms 
*/ 
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <map>
#include <cmath>
#include <iomanip>
#define INF 99999999
typedef long long LL;
using namespace std;

const int MAX=(1<<11)+10;
int n,m;
LL temp[MAX],dp[MAX],bin[15];

bool check(int i){
	while(i){
		if(i&1){
			i>>=1;
			if(!(i&1))return false;//第j列是1则第j+1列必须是1 
			i>>=1;//继续判断下一列 
		}else i>>=1;//继续判断下一列 
	}
	return true;
}

void Init(){
	memset(temp,0,sizeof temp);
	for(int i=0;i<bin[m];++i)if(check(i))temp[i]=1;//初始化第一行
}

void dfs(int k,int i,int j){
	if(k == m){dp[i]+=temp[j];return;}
	if(k>m)return;
	if((i>>k)&1){
		dfs(k+1,i,j);
		if((i>>(k+1))&1)dfs(k+2,i,j|(1<<k)|(1<<(k+1)));
	}
	else dfs(k+1,i,j|(1<<k));
}

void DP(){
	for(int k=2;k<=n;++k){
		for(int i=0;i<bin[m];++i)dp[i]=0;
		for(int i=0;i<bin[m];++i)dfs(0,i,0);
		for(int i=0;i<bin[m];++i)temp[i]=dp[i];
	}
}

int main(){
	bin[0]=1;
	for(int i=1;i<12;++i)bin[i]=2*bin[i-1];
	while(~scanf("%d%d",&n,&m),n+m){
		if(n<m)swap(n,m);//始终保持m<n,提高效率 
		Init();
		DP();
		printf("%lld\n",temp[bin[m]-1]);//输出最后一行到达时的状态必须全部是1 
	}
	return 0;
}



   

你可能感兴趣的:(poj2411之用1*2砖块铺满n*m-状态压缩dp)