291. 蒙德里安的梦想(状态压缩dp详解)

文章讲述了如何计算将N×M的棋盘分割成1×2的长方形的方案数,通过动态规划和状态压缩的方法,特别是利用二进制来表示棋盘的状态,避免了暴力搜索导致的时间超限问题。

求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式

每个测试用例输出一个结果,每个结果占一行。

数据范围

1≤N,M≤11

输入样例:

1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0

输出样例:

1
0
1
2
3
5
144
51205

解题思路:

题目是要考虑其木块是横放还是竖放,我们可以用dfs进行暴力搜索,但是这样子会超时。

这时候我们可以只考虑一种情况,我们只放横放的木块时,其他空位一定是竖放。这是就可以计算其总数了。

看到了m<12时,我们用二进制数表示更为简单。

状态压缩:

用二进制表示3状态

用十进制数存储状态

1、用1表示横放并且下一列不能放东西,用0表示竖放。

2、我们按列进行摆放。每一列不能出现连续个奇数0,因为出现了奇数个0意味着竖放不满足(在列中竖放一个要占据2个0)。

3、在考虑第i行和第i-1行的关系,这就是动态规划的意义的,进行递推。

例如;第i-1行为 1001 第i行可以是 0110 ,这时是要考虑兼容的情况了。

当i-1列和第i列&时为0,就是他们没有冲突时,在判处第2种情况时。就是满足题意的。

解题代码

#include<bits/stdc++.h>
using namespace std;
const int N = 12, M = 1 << N;
int n, m;
long long f[N][M];
bool st[M];

int main() {
	while (cin >> n >> m, n | m) {
		for (int i = 0; i < 1 << n; i++) { // n位  n - 1个
			int cnt = 0;
			st[i] = true;
			for (int j = 0; j < n; j++) {
				if (i >> j & 1) {
					if(cnt & 1 ) // 如果连续得0得个数位奇数是    这是因为竖放得位置个数要为2
					st[i] = false;
				}
				else {
					cnt++;
				}
			}
			// 处理最高为的0的个数
			if (cnt & 1) st[i] = false; 
		}

		memset(f, 0, sizeof(f));
		f[0][0] = 1;
		for (int i = 1; i <= m; i++) {
			for (int j = 0; j < 1 << n; j++) { //第i列的状态和 i-1是否兼容
				for (int k = 0; k < 1 << n; k++) { // i -l
					if ((j & k) == 0 && st[j | k])
					{ // j|k 是兼容后不出现奇数个相邻的0
						f[i][j] += f[i - 1][k];
					}
				}
			}
		}
		cout << f[m][0] << endl;
	}
	return 0;
}

<think>好的,我现在要讲解状态压缩动态规划(状态压缩DP)和蒙德里安问题。首先,我需要确保自己对这两个概念有足够的理解,然后才能清晰地传达给用户。 状态压缩DP,我记得它是一种动态规划的优化方法,用于处理状态空间较大的问题,尤其是当状态可以用位运算来表示的时候。比如在旅行商问题(TSP)或者棋盘覆盖问题中,状态可能涉及多个位置是否被访问过,这时候用二进制位来压缩状态,可以节省空间和时间。不过,具体的实现细节可能需要再复习一下,比如状态转移方程的设计,以及如何用位运算来高效处理状态。 然后是蒙德里安问题,可能指的是用特定的方块(比如1x2或2x1的砖块)来铺满一个矩形区域的方案数计算。这个问题通常可以用动态规划来解决,而状态压缩DP在这里可能被用来表示每一行的铺设状态,从而进行状态转移。比如经典的铺砖问题中,每一行的状态可以用二进制表示,0表示未覆盖,1表示已覆盖,然后通过状态之间的兼容性来转移。 接下来,我需要整理这两个概念的关系,以及如何用状态压缩DP解决蒙德里安问题。可能需要举一个具体的例子,比如计算一个N×M的棋盘用1×2的砖块铺满的方案数。这时候每一行的状态可以用二进制表示,状态之间的转移需要满足两行之间不会产生冲突,比如上一行的竖放砖块会影响下一行的状态。 需要注意的是,状态压缩DP的关键点在于如何高效地表示状态,并设计合适的状态转移方式。对于蒙德里安问题,可能还需要预处理所有合法的相邻行状态组合,以减少计算量。另外,初始化条件和边界情况也需要处理得当,比如第一行的合法状态可能只有某些特定的模式。 在讲解过程中,可能会遇到的疑问点包括:如何用二进制位表示行的状态,如何处理状态之间的兼容性,状态转移的具体实现步骤,以及如何优化时间复杂度。需要确保这些部分解释清楚,避免让用户感到困惑。 可能需要参考一些经典例题的解法,例如LeetCode上的相关题目或者《算法竞赛入门经典》中的例子,以确保所讲的内容正确无误。同时,要注意用中文表达,避免使用过于专业的术语,保持讲解的通俗易懂。 最后,总结状态压缩DP蒙德里安问题中的应用,强调其如何通过压缩状态来高效解决问题,以及这种方法适用的场景和限制。</think>状态压缩动态规划(状态压缩DP)和蒙德里安问题都是算法中经典的组合优化问题,下面分步讲解: --- ### **一、状态压缩DP** **核心思想**:将复杂状态用二进制数压缩表示,通过位运算高效处理状态转移。 **适用场景**: - 状态维度较高(如网格、棋盘) - 每个位置仅有两种状态(如选中/未选中) - 典型问题:旅行商问题、棋盘覆盖、铺砖问题 **关键步骤**: 1. **状态表示**:用二进制数每一位代表一个位置的状态(例如:1表示已覆盖,0表示未覆盖) 2. **状态转移**:通过位运算(如与、或、异或)判断相邻状态是否合法 3. **预处理**:提前计算合法状态集合,减少重复判断 **示例**: 计算3×2网格用1×2砖块铺满的方案数: ```plaintext 合法状态转移(二进制表示): 上一行状态 110 → 当前行需为 001 (竖放砖块的下半部分) ``` --- ### **二、蒙德里安问题** **问题描述**:用1×2或2×1的砖块铺满N×M的矩形区域,求方案总数。 **关键发现**: 当确定所有横放砖块的位置后,竖放砖块的位置唯一确定。因此只需计算横放砖块的合法方案。 **状态压缩DP解法**: 1. **状态定义**: `dp[i][s]` 表示处理到第`i`行,且第`i`行状态为`s`的方案数。 (`s`的二进制每一位表示该列是否被横放砖块的末尾覆盖) 2. **合法性判断**: - 当前行状态`s`不能有连续奇数个0(否则竖放砖块无法填充) - 相邻行状态`prev`与`s`需满足 `(prev & s) == 0`(横放砖块不重叠) 3. **转移方程**: ```python if prev和s兼容: dp[i][s] += dp[i-1][prev] ``` **经典优化**: 预处理所有合法相邻状态组合,时间复杂度从`O(N*2^M*2^M)`降为`O(N*K)`(`K`为合法组合数) --- ### **三、蒙德里安问题示例(2×3网格)** 1. **合法行状态**: - 二进制表示:`000`(全竖放)、`110`(横放前两列) 2. **状态转移**: ```plaintext 第1行状态110 → 第2行状态001 → 总方案数1 ``` 3. **最终结果**:3种方案 --- ### **四、总结** - **状态压缩DP**通过二进制高效处理高维状态,是解决棋盘类问题的利器。 - **蒙德里安问题**的解法体现了“先横后竖”的分解思想,结合状态压缩可将指数复杂度优化为可接受范围。 - 实际代码需注意位运算技巧(如判断连续偶数个0可用`(s | (s<<1)) & 3`循环检测)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值