POJ 2411 Mondriaan's Dream (状压DP)

题目类型  状压DP

题目意思
给出一个 n * m (1 <= n, m <= 11)的空白矩阵 问用 1×2 或 2×1 的小矩阵填満整个空白矩阵的方法数有多少个

解题方法
由于行和列最多只有11 所以可以用状态压缩 把一行的放置状态压缩为一个整数然后进行dp
dp[i][j] -> 前i行 第i行状态为j的方法数有多少个
其中状态 j 的规则 : 对于第i行 某一位上如果是被 1 * 2 (即横放)的小矩阵覆盖 或 被 2*1的小矩阵的下面一格覆盖 , 则这一位为 1 其他情况这一位为 0 
这样设计规则的话 第 i-1 行向 第 i 行转移时 (第 i 行的状态只和第 i-1 行状态有关和i-1上面的行均无关系, 因为那些行的状态影响不了第 i 行)
如果第 i-1行有0状态位的话 那么第 i 行对应位肯定要置为 1 (即放置一个2×1的小矩阵) 然后就可以枚举第 i 行其他未设状态的位的情况 (可以用DFS)
其他位这时只有一种放置情况就是连续两格放一个 1*2 的小矩阵 (因为放置2×1的小矩阵的话这一位依然是0相当于没放, 这一个情况是会被下一行
放置一个 2*1的小矩阵所考虑的) 
详细转移过程看代码 可以用滚动数组优化内存

参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

typedef long long LL;
LL dp[2][2100];
bool vis[20];
int m, now;

void DFS(int i, int j) {
	if(i == m) {
		int tmp = 0;
		for( int k=0; k<m; k++ ) if(vis[k]) tmp += (1<<k);
		dp[now^1][tmp] += dp[now][j];
		return ;
	}
	if(i+1 < m && vis[i] == false && vis[i+1] == false) {
		vis[i] = vis[i+1] = true;
		DFS(i+2, j);
		vis[i] = vis[i+1] = false;
	}
	if(i < m) DFS(i+1, j);
}

int main() {
	int n;
	while(scanf("%d%d", &n, &m), n || m) {
		now = 0;
		memset(dp, 0, sizeof(dp));
		dp[now][(1<<m)-1] = 1;  //边界值, 刚开始假设最上面一行上面有一行已经填满的行
		for( int i=0; i<n; i++ ) {
			for( int j=0; j<(1<<m); j++ ) { // 枚举第 i-1 行的状态去更新第 i 行, 第-1行就是刚刚的边界值
				if(dp[now][j] == 0) continue;
				memset(vis, 0, sizeof(vis));
				for( int k=0; k<m; k++ ) {
					if((j & (1<<k)) == 0) vis[k] = true; // 首先把 要放置 2×1矩阵的先放了
				}
				DFS(0, j); // 再枚举放 1*2矩阵的
			}
			now ^= 1;  //滚动数组优化内存
			for( int j=0; j<(1<<m); j++ ) dp[now^1][j] = 0;
		}
		cout<<dp[now][(1<<m)-1]<<endl; // 注意要用 long long
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值