POJ2411 Mondriaan's Dream 题解 轮廓线DP
题目链接:http://poj.org/problem?id=2411
题目大意
给你一个 \(n \times m (1 \le n,m \le 11)\) 的矩阵,你需要用若干 \(1 \times 2\) 的砖块铺满这个矩阵。
要求不能有砖块重叠,并且矩阵中的每个各自都需要铺满。
比如下图中描述的就是一个 \(10 \times 11\) 的矩阵的一种合法的铺法。
问满足要求的 方案数 。
比如下图中的左边5幅图片对应的是 \(2 \times 4\) 的矩阵的所有合法方案;右边的3幅图片对应的是 \(2 \times 3\) 的矩阵的所有合法方案。
解题思路
轮廓线DP 的思想建立在如下一种思维模式下:
我按行号从小到大放,相同行的情况下我按照列号从小到大放。
那么在某一个时刻我总能达到这样一个场景,如下图:
在同种我们用3种颜色描绘了三种不同状态的点:
- 蓝色的点:表示必须放置好的确定点(置1);
- 绿色的点:表示我当前正在考虑的不确定点;
- 红色的点:表示绿色的点前面的 \(m\) 个不确定点。
解释:
我这里称呼的“确定点”指的是已经放好砖块的点;
“不确定点”指的是不确定有没有放的点(可能放了也可能没放)。
我们用数字 1 表示放了;用数字 0 表示没放。
我们假设左上角坐标是 \((1,1)\) (实际实现的时候其实坐标不一定,具体看代码对应的左上角坐标),并且设我们当前正在遍历的点是 \((i,j)\) ,那么:
如果 \(j=1\) ,则说明
在处理 \((i,j)\) 之前, \((1,1)\) 到 \((i-2,m)\) 都是确定点,如图:
在处理 \((i,j)\) 之后, \((1,1)\) 到 \((i-1,1)\) 都是确定点,如图:
如果 \(j \gt 1\) ,则说明
在处理 \((i,j)\) 之前, \((1,1)\) 到 \((i-1,j-1)\) 都是确定点,如图:
在处理 \((i,j)\) 之后, \((1,1)\) 到 \((i-1,j)\) 都说是确定点,如图:
所以我们可以用状态 \(f_{i,j,k}\) (其中 \(1 \le i \le n, 1 \le j \le m, 0 \le k \le 2^m\) )来表示当前我们遍历到 \((i,j)\) 时,以 \((i,j)\) 结尾的 \(m\) 个元素的状态为 \(k\) 时的方案总数( \(k\) 的二进制表示的第0位对应 \((i,j)\) 目前有无放置,第1位对应 \((i,j)\) 的前一个格子目前有无放置,……)
那么如何放置呢?我们可以粗略地分为如下四种情况:
- 情况(1):只能竖着填,因为绿色上面的红色必须要被覆盖,不然之后就不会被覆盖了。
- 情况(2):可以横着填,也可以不填。
- 情况(3):只能竖着填,同理,上面的这次不被覆盖就没有机会被覆盖了。
- 情况(4):只能不填,横竖都填不了。
然后我们可以顺着推到状态转移方程。
初始状态是 \(f(0,m,不确定区域全为1)=1\) ,这个状态其实是表示第 \(0\) 行全都填满(因为第 \(0\) 行不可能被填所以就干脆设该状态为不确定区域全为1)对应的方案数为 \(1\) 。
另一方面需要注意:注意每一排第一个是没有办法横着摆的!
最后输出 \(f(n,m,不确定区域全部为1)\) 即可。
实现代码如下:
#include <cstdio>
#include <cstring>
int n, m;
long long f[13][13][1<<13];
int main() {
while (~scanf("%d%d", &n, &m) && n) {
memset(f, 0, sizeof(f));
f[0][m-1][(1<<m)-1] = 1;
for (int r = 1; r <= n; r ++) { // 枚举当前行号r
for (int c = 0; c < m; c ++) { // 枚举当前列号c
int pr, pc; // pr,pc分别表示上一个状态的行号和列号
if (!c) pr = r-1, pc = m-1;
else pr = r, pc = c-1;
for (int k = 0; k < (1<<m); k ++) { // k表示上一个状态
/**
情况1:当前位置不放置木板,这种情况下要求 上一个状态的首个不确定格子是放置了木板的,
即: k & (1 << (m-1)) != 0
*/
if (k & (1 << (m-1))) { // 说明上一个状态的最前面的格子已填充
int s = (k << 1) ^ (1 << m); // s表示当前状态
f[r][c][s] += f[pr][pc][k];
}
/**
情况2:当前位置放置一块竖着放的木板,这种情况下要求
上一个状态的首个不确定格子是没有放置模板的,
即: k & (1 << (m-1)) == 0
*/
if (r > 1 && (k & (1 << (m-1))) == 0) { // 说明上一个状态的最前面的格子(即当前状态的上面的那个格子)未填充
int s = (k << 1) ^ 1; // s表示当前状态
f[r][c][s] += f[pr][pc][k];
}
/**
情况3:当前位置放置一块横着放的木板,这种情况下要求
上一个状态的最后一个不确定格子是没有放置模板的,
并且要求上一个状态的最前面一个不确定格子是必须放置模板的(此时不放,没有别的时间放!),
即: k & 1 == 0
*/
if (c > 0 && (k & 1) == 0 // 说明上一个状态的最后面的格子(即当前状态的左边的那个格子)未填充
&& (k & (1 << (m-1))) ) { // 说明上一个状态的最前面的格子已填充
int s = (k << 1) ^ 3 ^ (1 << m); // s表示当前状态
f[r][c][s] += f[pr][pc][k];
}
}
}
}
printf("%lld\n", f[n][m-1][(1<<m)-1]);
}
return 0;
}
滚动数组
在实现的过程中,因为我们发现:当前的这个状态 \(f_{i,j,s}\) 和它的上一步状态 \(f_{i',j',s'}\) 是有着位置上的衔接关系的,所以我们可以开一个滚动数组来表示位置。
具体地,将 \(f_{i,j,s}\) 用 \(f_{now,s}\) 来表示,而将 \(f_{i',j',s'}\) 用 \(f_{now^1, s'}\) 来表示。
实现代码如下(注意这里行号和列号都从0开始了):
#include <cstdio>
#include <cstring>
int n, m;
long long f[2][1<<13];
int main() {
while (~scanf("%d%d", &n, &m) && n) {
memset(f[0], 0, sizeof(f[0]));
int now = 0;
f[now][(1<<m)-1] = 1;
for (int r = 0; r < n; r ++) { // 枚举当前行号r
for (int c = 0; c < m; c ++) { // 枚举当前列号c
now ^= 1;
memset(f[now], 0, sizeof(f[now]));
for (int k = 0; k < (1<<m); k ++) { // k表示上一个状态
/**
情况1:当前位置不放置木板,这种情况下要求 上一个状态的首个不确定格子是放置了木板的,
即: k & (1 << (m-1)) != 0
*/
if (k & (1 << (m-1))) { // 说明上一个状态的最前面的格子已填充
int s = (k << 1) ^ (1 << m); // s表示当前状态
f[now][s] += f[now^1][k];
}
/**
情况2:当前位置放置一块竖着放的木板,这种情况下要求
上一个状态的首个不确定格子是没有放置模板的,
即: k & (1 << (m-1)) == 0
*/
if (r > 0 && (k & (1 << (m-1))) == 0) { // 说明上一个状态的最前面的格子(即当前状态的上面的那个格子)未填充
int s = (k << 1) ^ 1; // s表示当前状态
f[now][s] += f[now^1][k];
}
/**
情况3:当前位置放置一块横着放的木板,这种情况下要求
上一个状态的最后一个不确定格子是没有放置模板的,
并且要求上一个状态的最前面一个不确定格子是必须放置模板的(此时不放,没有别的时间放!),
即: k & 1 == 0
*/
if (c > 0 && (k & 1) == 0 // 说明上一个状态的最后面的格子(即当前状态的左边的那个格子)未填充
&& (k & (1 << (m-1))) ) { // 说明上一个状态的最前面的格子已填充
int s = (k << 1) ^ 3 ^ (1 << m); // s表示当前状态
f[now][s] += f[now^1][k];
}
}
}
}
printf("%lld\n", f[now][(1<<m)-1]);
}
return 0;
}
参考链接
POJ2411 Mondriaan's Dream 题解 轮廓线DP的更多相关文章
- poj2411 Mondriaan's Dream (轮廓线dp、状压dp)
Mondriaan's Dream Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 17203 Accepted: 991 ...
- 【POJ2411】Mondriaan's Dream(轮廓线DP)
[POJ2411]Mondriaan's Dream(轮廓线DP) 题面 Vjudge 题解 这题我会大力状压!!! 时间复杂度大概是\(O(2^{2n}n^2)\),设\(f[i][S]\)表示当前 ...
- POJ - 2411 Mondriaan's Dream(轮廓线dp)
Mondriaan's Dream Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One nig ...
- [poj2411] Mondriaan's Dream (状压DP)
状压DP Description Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One nigh ...
- POJ2411 - Mondriaan's Dream(状态压缩DP)
题目大意 给定一个N*M大小的地板,要求你用1*2大小的砖块把地板铺满,问你有多少种方案? 题解 刚开始时看的是挑战程序设计竞赛上的关于铺砖块问题的讲解,研究一两天楞是没明白它代码是怎么写的,智商捉急 ...
- poj 2411 Mondriaan's Dream (轮廓线DP)
题意:有一个n*m的棋盘,要求用1*2的骨牌来覆盖满它,有多少种方案?(n<12,m<12) 思路: 由于n和m都比较小,可以用轮廓线,就是维护最后边所需要的几个状态,然后进行DP.这里需 ...
- POJ2411 Mondriaan's Dream 轮廓线dp
第一道轮廓线dp,因为不会轮廓线dp我们在南京区域赛的时候没有拿到银,可见知识点的欠缺是我薄弱的环节. 题目就是要你用1*2的多米诺骨排填充一个大小n*m(n,m<=11)的棋盘,问填满它有多少 ...
- $POJ2411\ Mondriaan's\ Dream$ 状压+轮廓线$dp$
传送门 Sol 首先状压大概是很容易想到的 一般的做法大概就是枚举每种状态然后判断转移 但是这里其实可以轮廓线dp 也就是从上到下,从左到右地放方块 假设我们现在已经放到了$(i,j)$这个位置 那么 ...
- poj2411 Mondriaan's Dream【状压DP】
Mondriaan's Dream Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 20822 Accepted: 117 ...
随机推荐
- ORACLE SQL数据类型转换
ORACLE SQL数据类型转换 2019-04-07 22:35:53 广小白 阅读数 429更多 分类专栏: Oracle 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议, ...
- H3C 代理ARP
- Unity 鼠标控制视角功能和动画播放冲突解决办法
环境是 unity 5.6.4 情况说明: 1 模型动画单独播放是没问题的. 2 鼠标控制模型是没问题的. 3 在start中播放模型动画,即使鼠标控制视角代码还挂载着,但是模型却无法用鼠标旋转等操作 ...
- js读取cookie 根据cookie名称获取值、赋值
借鉴:原作者https://blog.csdn.net/zouxuhang/article/details/80548417 //方法1 //存在问题:如果cookie中存在 aaaname= ...
- HDU 1372
题意:模拟国际象棋马的走棋方式,和中国象棋一样马走日,8X8的棋盘,问从起点到终点的最短步数,国际象棋中数字代表行row,字母代表列column, 思路:记忆化深搜. #include<cstd ...
- PyTorch里面的torch.nn.Parameter()
在刷官方Tutorial的时候发现了一个用法self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size)),看了官方教程里面的解释也是云里雾里, ...
- C# 配置文件存储 各种序列化算法性能比较
本文比较多个方式进行配置文件的存储,对比各个不同算法的读写性能. 在应用软件启动的时候,需要读取配置文件,但是启动的性能很重要,所以需要有一个很快的读取配置文件的方法. 如果你不想看过程,那么请看拖动 ...
- Python--day40--主线程和子线程代码讲解
1,最简单的线程例子: 2,多线程并发: import time from threading import Thread #多线程并发 def func(n): time.sleep(1) prin ...
- 安装scipy失败提示lapack not found
从python库网站下载numpy+mkl合集包通过pip安装在下载scipy安装包通过pip安装即可
- P1062 差K素数对
题目描述 给你两个数 n 和 k ,请求出所有小于等于 n 的相差为 k 的素数对. 输入格式 两个正整数n,k.1<=k<=n<=10000. 输出格式 所有小于等于n的素数对.每 ...