AcWing 状态压缩DP——291. 蒙德里安的梦想

原题链接:

291. 蒙德里安的梦想 - AcWing题库

题解:

状压DP确实挺难的,本题无论是状态表示还是状态计算都很难想到。

这篇文章说的非常清楚,这里就不再赘述。

Acwing291. 蒙德里安的梦想:状态压缩dp-CSDN博客

疑惑点:

为什么初始化的时候f[0][0] = 1?
首先,题目中输入参数的列数是从1开始到m,即范围为1~m,但我们写的时候是将其先映射到数组0~m-1里;
对于第一列,也就是数组中的第0列,是需要初始化的;也就是我们需要初始化f[0][x] = ?
回到定义,f[0][x] 表示从-1列伸到0列(此处说的都是数组下标)状态为x的方案。我们发现,合法的方案只能是不伸过来,因为根本没有-1列。即x只能取0的时候方案合法,f[0][0] = 1;接着dp过程就从第1列(数组下标)开始。

答案为什么是f[m][0] 呢?

因为横放的时候方块最多够到第m-1列(数组下标),不能从m-1再往外伸,
所以是f[m][0];

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 12, M = 1 << N;
using LL = long long;
LL f[N][M];
bool st[M];//存储每种状态是否有奇数个连续的0,如果奇数个0是无效状态,如果是偶数个零置为true。
vector<vector<int>> sta(M);//下标为i处存储第i-1列的所有合法状态j

int main(){
	int n, m;
	while (cin >> n >> m, n || m) {
		//Step1:预处理->明确哪些状态合法,哪些不合法(某列|状态是否存在连续的奇数个0区域)
		for (int i = 0;i < (1 << n);i++) {
			bool tag = 1;//该状态是否为非法状态
			int cnt = 0;//当前状态所遇到的连续0个数
			for (int j = 0;j < n;j++) {
				if ((i >> j) & 1) {
					if (cnt & 1) {
						tag = 0;
						break;
					}
				}
				else cnt++;
			}
			if (cnt & 1) tag = 0;
			st[i] = tag;
		}
		//Step2:预处理2->进一步明确哪些状态合法,哪些不合法(某列|状态i-1是否被i-2插入的同时自身插入到i,发生重叠现象)
		for (int i = 0;i < (1 << n);i++) {
			sta[i].clear();//因为输入多个样例,需要清除上一个样例的遗留结果
			for (int j = 0;j < (1 << n);j++) {
				if (((i & j) == 0) && st[i | j]) sta[i].push_back(j);//如果两个没重叠且组合后该状态为合法状态,则将j状态加入i的状态转移
			}
		}
		//Step3:dp
		memset(f, 0, sizeof(f));
		f[0][0] = 1;
		for (int i = 1;i <= m;i++) {//遍历每一列:第i列合法范围是(0~m-1列)
			for (int j = 0;j < (1 << n);j++) {//遍历当前列(第i列)所有状态j
				for (auto k : sta[j])// 遍历第i-1列的状态k,如果“真正”可行,就转移
					f[i][j] += f[i - 1][k];
			}
		}
		cout << f[m][0] << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值