poj 2411 Mondriaan's Dream (状态dp)

本文探讨了一个关于使用1*2和2*1两种砖块填充N*M网格的问题,并提出了一种利用二进制状态表示的方法来求解所有可能的填充方案。通过定义状态转移规则,实现了高效的动态规划算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

这个题目的题意很容易理解,在一个N*M的格子里,我们现在有两种类型的 砖块,1 * 2 和 2 * 1,问一共有多少种方案,可以将整个N*M的空间都填满。

最简单的例子就是下面的了:

题解:

状态标记 横放和竖放的下一个均为1,竖放的上一个和不放置为,每行可以转化为12进制数。为什么要这样呢,应为这样表示肯定是包括了pre(前一行)和now(后一行)的所有状态的(而且多了很多,后面的主要工作是怎样删掉这些多的),而且可以区分,还容易区分。也就是说从pre转到now状态时要做的事不是很多。

或者还有一种理解方法:pre为1表示now与pre无关,为0就表示有关;也就是:

1. 在横着贴砖的时候,(i, j), (i, j+1) 都是1,这个值其实对下一行如何选择没有影响。

2. 竖着贴砖的第二个,我们也选择了1, 因为这个砖头结束了,对下一行如何选择依然没有影响。

3. 而竖着的第一个砖头,这个砖头是对下面有影响的,如果(i,j)是0,那么(i+1, j)只有是1的情况下才能满足条件。

那好下面介绍3种方法(越来越叼):

一:暴力:就是三重循环,如果pre的状态k与now的状态j匹配的话dp[i][j] += dp[i-1I][k];

然后懒得写了,具体想知道就看代码和注释吧:

//这个代码是直接复制别人的;
#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];/*这个滚动有点瓜皮^.^;
	}
	  /*
        那他上面那两个判断就很有灵性了;可能很多人还没理解;我在举例子来说明一下。
        首先我们那样定义就是有两个地方要解决,第一个是你不能在第i-1行放一个竖的(放0)又在第i行放一个竖的;
        也就是双零情况,所以只要满足i|j是满的(每位都为一)就行了;
        第二种就是不能放半个横的;又懒得讲了,自己举个例子就好了;
        */
}

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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值