您的位置:首页 > 其它

poj 2411 Mondriaan's Dream (状态dp)

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

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


题解:

状态标记 横放和竖放的下一个均为1,竖放的上一个和不放置为0 ,每行可以转化为1个2进制数。为什么要这样呢,应为这样表示肯定是包括了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;
}

好,那现在介绍第二种方法:

上面是先枚举pre和now的状态,再看他们是否匹配;

  你会发现很多状态都是不匹配的;

现在是先枚举now的状态,在去搜索可用的pre状态;或者先枚举pre再搜now也行;

代码:

哦,令外说一点就是求第一行的所有可用状态,可以假设有0行,dp[1][j] = dp[0][1<<m-1],相当于第一行是第零行全为1的状态发展而来(第一行与第0行无关,自由发挥),但是比直接求会慢一点;

//这就是我自己写的;
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
using namespace std;
const int MAXN = 16;
int sPath, m, v;
long long dp[2][1<<11];

void dfs(int k, int pre, int now) {//dfs自行体会
if (k == m) {
dp[v^1][now] += dp[v][pre];
return;
}
if (now>>k&1) {
dfs(k+1, pre, now);
if (k+1 < m && now>>(k+1)&1) dfs(k+2, pre|1<<k|1<<(k+1), now);
}
b7dd

else dfs(k+1, pre|1<<k, now);
}

int main() {
int n;
//freopen("in.txt", "r", stdin);
while(scanf("%d%d", &n ,&m)) {
if (!n && !m) break;
if (n&1 && m&1) {
printf("0\n");
continue;
}
if (n < m) {
n = n^m;
m = n^m;
n = n^m;
}
v = 0;
memset(dp[0], 0, sizeof(dp[0]));
dp[0][(1<<m)-1] = 1;//这就是我注意说的,
for(int i = 1; i <= n; i++) {
memset(dp[v^1], 0, sizeof(dp[v^1]));
//if(i == 1) j = (1<<m)-1; //可以这样优化一下,就差不多了;
for(int j = 0; j < 1<<m; j++)
dfs(0, 0, j);
v ^= 1;//这样滚才有灵性
}
printf("%I64d\n", dp[v][(1<<m)-1]);
}
}

方法三:
你们发现一个东西没,每次从pre转到now时都是一样的东西;比如第一行的i状态可以转到第二行的j;那么第二行的i肯定也可以转到第3行的j;
这样的话,就可以先把所有的状态都先存下来,然后直接取出来用。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
using namespace std;
const int MAXN = 16;
int path[1<<16][2], sPath, m; //path小了会蜜汁re哦;
long long dp[2][(1<<11)];

void dfs(int k, int pre, int now) {
if (k == m) {
path[sPath][0] = pre;
path[sPath++][1] = now;
return ;
}
dfs(k+1, pre<<1|1, now<<1);
dfs(k+1, pre<<1, now<<1|1);
if (k+2 <= m) dfs(k+2, pre<<2|3, now<<2|3);
}

int main() {
int n, v;
while(scanf("%d%d", &n ,&m)) {
if (!n && !m) break;
if (n&1 && m&1) {
printf("0\n");
continue;
}
if (n < m) {
n = n^m;
m = n^m;
n = n^m;
}
sPath = 0;
dfs(0, 0, 0);
v = 0;
memset(dp[0], 0, sizeof(dp[0]));
dp[0][(1<<m)-1] = (long long)1;
for(int i = 1; i <= n; i++) {
memset(dp[v^1], 0, sizeof(dp[v^1]));
for(int j = 0; j < sPath; j++)
dp[v^1][path[j][1]] += dp[v][path[j][0]];
v ^= 1;
}
printf("%I64d\n", dp[v][(1<<m)-1]);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poj 2411 状态dp