[状压dp] poj 2411 Mondriaan's Dream

2023-11-20 20:10
文章标签 dp poj 状压 dream 2411 mondriaan

本文主要是介绍[状压dp] poj 2411 Mondriaan's Dream,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文
原作者:zthgreat

Mondriaan’s Dream


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

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

经典覆盖问题,输入n和m表示一个n*m的矩形,用1*2的方块进行覆盖,不能重叠,不能越出矩形边界,问完全覆盖完整个矩形有多少种不同的方案

下面分析过程属于部分转载:http://blog.csdn.net/hopeztm/article/details/7841917?utm_source=tuicool&utm_medium=referral

这个题目类属于状态压缩DP,对于状态压缩DP,其实最简单的理解就是把状态用比特位的形式表示出来,我们会在下面用例子来说明。

假如现在我们在铺砖 位置(i, j), 并且假设之前的位置已经铺设好的了,在这个位置,我们的选择:

1. 不用铺砖了,可能在(i-1, j)的时刻已经被竖着铺上了,然后考虑的是(i, j+1)

2. 横铺砖,将(i, j+1)也铺上了,然后考虑的是(i, j+2)

3. 竖着铺砖,(将i,j)和(i+1,j)铺上一个竖立的转头。


所以我们如下翻译我们的选择,在位置(i, j) 如果我们选择横着贴砖,那么将(i, j), (i, j+1)都填写成1, 如果竖着贴砖,我们将(i,j)填写成0, 将(i+1, j)填写成1.

为什么要这么计数呢,我觉得应该这样理解:

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

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

3. 而竖着的第一个砖头,这个砖头是对下面有影响的,如果(i,j)是0,那么(i+1, j)只有是1的情况下才能满足条件。(这涉及到接下来的 状态兼容性问题)


对于竖着贴砖为什么这样选择,这样选择的一个好处是,我们在处理最后一行的时候,可以保证最后一行都是1, 因为最后一行绝对不能成为 竖砖开始,所以很容易取得最后的解。

好了,我们把这样理解的方案画成图:

如果我们将每一行都理解成一个二进制数字,那么

Row1 = 51,  Row2 = 15, Row3 = 48, Row4 = 63, Row5 = 51, Row6 = 63.

最后转头铺满的状态,一定是最后一行全是1。

我们用DP(i,j) 表示如下含义: 当第i行,达到状态j的时候,所能采取的方案数目。 所以明显我们的最后目的是求 DP(N, 2^M-1);


我们再来简单的分析一下为什么问题可以满足动态规划, 加入现在分析的对象是 DP(i,j), 那么这一行有多少种铺设办法是和上一行相关的,

如果上一行的某个状态DP(i-1,k) 可以达到 DP(i, j) 我们认为这两个状态是兼容的,如果DP(i-1,k)和DP(i, j)兼容并且 DP(i-1, k)有S中铺设方案,那么DP(i, j)就可以从DP(i-1, k)这条路径中获得S个方案。 当然这里k的取值可以是 0 ~~~~ 2^M -1种取值。


现在我们来理解一下,什么叫做 j, k 兼容。

其实我们在上面已经基本给出分析, 如果我们现在铺设 (i,x) x这里表示第i行,第x列

1. 如果值 i  行,j 在x位上的值是0, 那么第 i-1行,j的值在x位上一定是1。因为不可能在同一列相邻的位置铺两个竖着的 第一个,如果满足下一步测试的是(i, x+1), 否则直接返回不兼容。


2. 如果值 i  行,j在x位置的值是1 .

{


            那么有可能有两种情况:

            1. (i-1, x)是0, 这个时候一定是竖着铺设了,下一步检测的是(i, x + 1)

            

            2.  (i-1, x) 是1, 如果是这样的话,那么(i, x)一定是要选择横着铺了,那么(i,x+1)也一定是1,并且(i-1, x + 1)一定是1(如果是0,就是竖着铺了),如果不满足就返回不兼容,满足条件 就测试(i, x + 2)

               

}


对于第一行的兼容性,我们要做一下特别的分析,在第一行中,要么放0, 要么放1。

加入当前测试的是 DP(1, j)的第 x的比特位,即第1行,x列

1. 如果x是1,那么 x + 1 也一定是1,然后测试到 x + 2

2. 如果x是0, 那么直接测试下一个 x + 1

补充说明一点,当测试循环中,我们有时候必须要移动 1 位,有时候移动2位,当需要移动2位并且 x == M - 1(M列数)的时候,说明已经不可能兼容了。

下面贴出代码,并在代码中给予部分说明:
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define  mod 100000000
int n,m;
long long dp[15][1<<12];//设置初始状态
bool init(int status)
{for (int j=0; j<m;) //前j-1列符合要求,对第j列进行判断{if (status & (1<<j)) //第j列为1{if (j==m-1) //j为最后一列,不可行return false;if (status & (1<<(j+1))) //第j列和第j+1列都为1 则表示横放,可行,考虑 j+2列j+=2;else //第j列为1,第j+1列都为0不可行return false;}else //第j列为0 ,则为竖放,可行j++;}return true;
}
//判断上一次状态和本次状态是否兼容
bool check(int now_s,int pre_s)
{for (int j=0; j<m; ){if (now_s & (1<<j)) //第i行第j列为1{if (pre_s & (1<<j)) //第i-1行第j列也为1,那么第i行必然是横放{//第i行和第i-1行的第j+1都必须是1,否则是非法的if (j==m-1 || !(now_s & 1<<(j+1)) || !(pre_s & 1<<(j+1)))return false;elsej+=2;}elsej++; //第i-1行第j列为0,说明第i行第j列是竖放}else  //第i行第j列为0,那么第i-1行的第j列应该是已经填充了的{if (pre_s & (1<<j))j++;elsereturn false;}}return true;
}void solve()
{int tot=(1<<m)-1;memset(dp, 0, sizeof(dp));for (int s=0; s<=tot; s++){if (init(s))dp[1][s]=1;}for (int i=2; i<=n; i++) //按行dp{for (int j=0; j<=tot; j++) //第i行的状态{for (int k=0; k<=tot; k++) //第i-1行的状态{if (check(j, k))dp[i][j]+=dp[i-1][k];}}}printf("%lld\n",dp[n][tot]);
}
int main()
{while (scanf("%d%d",&n,&m) && (n||m)){if (n&1 && m&1) //n和m均为奇数的话,矩形面积就是奇数,可知是不可能完全覆盖的{printf("0\n");continue;}if (n<m) //交换n和m使n较大m较小,这样能减少状态数swap(n, m);solve();}return 0;
}

这篇关于[状压dp] poj 2411 Mondriaan's Dream的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/397131

相关文章

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

hdu 2602 and poj 3624(01背包)

01背包的模板题。 hdu2602代码: #include<stdio.h>#include<string.h>const int MaxN = 1001;int max(int a, int b){return a > b ? a : b;}int w[MaxN];int v[MaxN];int dp[MaxN];int main(){int T;int N, V;s

poj 1511 Invitation Cards(spfa最短路)

题意是给你点与点之间的距离,求来回到点1的最短路中的边权和。 因为边很大,不能用原来的dijkstra什么的,所以用spfa来做。并且注意要用long long int 来存储。 稍微改了一下学长的模板。 stack stl 实现代码: #include<stdio.h>#include<stack>using namespace std;const int M

poj 3259 uva 558 Wormholes(bellman最短路负权回路判断)

poj 3259: 题意:John的农场里n块地,m条路连接两块地,w个虫洞,虫洞是一条单向路,不但会把你传送到目的地,而且时间会倒退Ts。 任务是求你会不会在从某块地出发后又回来,看到了离开之前的自己。 判断树中是否存在负权回路就ok了。 bellman代码: #include<stdio.h>const int MaxN = 501;//农场数const int

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

poj 1287 Networking(prim or kruscal最小生成树)

题意给你点与点间距离,求最小生成树。 注意点是,两点之间可能有不同的路,输入的时候选择最小的,和之前有道最短路WA的题目类似。 prim代码: #include<stdio.h>const int MaxN = 51;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int P;int prim(){bool vis[MaxN];