Mondriaan's+Dream POJ 2411 状压dp

题目链接:poj 2411

题目大意:对于一个n*m的矩阵,可以放1*2的牌,横着放或者竖着放,问有多少种放的方法。

我们考虑每一行是把牌横着放还是竖着放,显然竖着放的时候会影响到下一行,所以我们把竖着放的位置用二进制的1表示,并且是表示这个位置是竖着放的牌的上半部分(可能有些绕 仔细理解一下) 

那么对于一行的状态我们用一个二进制的数如上面的说法表示,如何转移呢,要满足一下两点:

  1. 上一行的状态和本行的状态&运算为0 这个比较好理解 因为上一行为1的位置表示竖着放的牌的上半部分,在这一行肯定就是下半部分了,我们用二进制位的0表示,同理,另外这一行为1的位置肯定上一行不能为1。
  2. 上一行的状态和本行的状态进行|运算后得到的二进制数得满足:0都是偶数个连续出现的  这点我们仔细想想也能明白,上一行和本行或运算以后就可以表示本行哪些位置的牌是竖着放了(这些位置是1) 那么剩下的位置就是横着放的,肯定得是偶数个0才满足

  对于满足条件2的这些数我们可以预处理一下 然后以行为阶段就可以进行dp了 

f[i][j]表示前 i 行,当前行状态为 j,可以得到的方法数

我们枚举上一行的状态 k 对于满足条件的状态 f[i][j]+=f[i-1][k]

最终我们需要的答案是 f[n][0] (最后一行显然不需要一个竖着放的牌的上半部分)

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
bool check[1<<11];
ll f[12][1<<11];
int n,m;
int main(){
	while(scanf("%d%d",&n,&m),n||m){
		memset(check,false,sizeof(check));
		for(int i = 0; i < (1<<m); i++){//预处理部分
			int flag = 1,cnt = 0;
			for(int j = 0; j < m; j++){
				if(i>>j&1){
					if(cnt%2){
						flag=0;
						break;
					}
					cnt=0;  
				}else cnt++;
			}
			if(cnt%2) flag=0;
			check[i]=flag;
		}
		//for(int i = 1; i < (1<<m); i++) printf("i=%d che[i]=%d\n",i,check[i]);
		f[0][0]=1;
		for(int i = 1; i <= n; i++){
			for(int j = 0; j < (1<<m); j++){
				f[i][j]=0;
				for(int k = 0; k < (1<<m); k++)
				if(((j&k)==0)&&check[j|k]){
					f[i][j]+=f[i-1][k];
				}
			}
		}
		printf("%lld\n",f[n][0]);
	}
	return 0;
}
原创文章 85 获赞 103 访问量 2495

猜你喜欢

转载自blog.csdn.net/weixin_43824564/article/details/105675739