291. 蒙德里安的梦想 + 91. 最短Hamilton路径 状态压缩dp

目录

 291. 蒙德里安的梦想​编辑

AcWing 91. 最短Hamilton路径 


 状态压缩dp,总而言之就是将本来很复杂的状态,表示成二进制的形式,然后进行状态转移。

其中常会用到位运算操作,比如查看当前状态i的二进制形式第j个点(位置)是1还是0

可以这样,意思是将i像右移动j-1位,最后一位就是第j位,&1取出位数,然后就能判断了。

i>>(j-1)&1

 同理,状态压缩dp中会用一维用二进制描述状态,如果有n个待描述的位,那么总状态最大为1<

如果一个状态i的一位 j 是1,想得到不包括 j 这一位1的状态为 (i-(1<

 291. 蒙德里安的梦想291. 蒙德里安的梦想 + 91. 最短Hamilton路径 状态压缩dp_第1张图片

本题的主要思路是,首先发现,要想满满当当的覆盖大矩形,那么横着摆放的长方形确定了,竖着摆放的也会确定。,此时横着摆放完的空白行在每一列必定是长度为偶数的。

所以去求横着摆放,且满足条件的状态有多少种就行。

dp的思路在注释,还是很复杂的。

#include
using namespace std;
#pragma warning(disable:4996);
#define int long long 
#define endl '\n'
#define rep(j,b,e) for(int j=(b);j<=(e);j++)
#define drep(j,e,b) for(int j=(e);j>=(b);j--)
const int N = 2e5 + 10;
int mod = (1e7) + 7;
int T = 1;
int n, m, x, k;
int dp[15][N];//第i列的状态j,表示i-1横向放置的长方形突到i列的个数
int st[N];
signed main() {
#ifndef ONLINE_JUDGE
	//freopen("out.txt", "w", stdout);
#endif
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	while (cin >> n >> m and (n || m)) {
		memset(dp, 0, sizeof(dp));
		rep(j, 0, (1 << n)) {//初始化一个数二进制是否只有偶数个0
			st[j] = 1;
			int cnt = 0;//记录连续0的个数
			rep(i, 0, n - 1) {//取每位的二进制位
				int t = (j >> i);
				if (t & 1) {//判断最后一位二进制位是1
					if (cnt % 2 == 1) {//出现了奇数个0,不合法
						st[j] = 0;
						cnt = 0;
					}
				}
				else
					cnt++;//0个数++
			}
			if (cnt % 2 == 1) {//结算最后的0个数。出现了奇数个0,不合法
				st[j] = 0;
			}
		}
		//---状态压缩dp
		/*
		思路是用二进制表示状态,1表示前一列当前行横着摆放伸到第i列的状态
		同时要满足 前一列的1不与当前行的1冲突(因为横放的方块长度为2)i&k==0
		且一列的空白行必须为全为偶数长,才能放下竖着的方块
		*/
		dp[1][0] = 1;//初始化,第一列啥都没有,只有一种方案
		rep(j, 2, m + 1) {//枚举所有列(1-m)
			rep(i, 0, (1 << n) - 1) {//枚举当前列的状态,合法的就能转移
				rep(k, 0, (1 << n) - 1) {//枚举上一列的状态
					if ((i & k) == 0 and st[(i | k)]) {//如果和上一个状态不冲突,没有重叠的横向矩形而且没有奇数空白
						dp[j][i] += dp[j - 1][k];
					}
				}
			}
		}
		cout << dp[m + 1][0] << endl;//最后一列m不应该用突出来的
	}
}

AcWing 91. 最短Hamilton路径

 291. 蒙德里安的梦想 + 91. 最短Hamilton路径 状态压缩dp_第2张图片

和最小生成树有点像,但区别是此时只需要一条路径,每个点只经过一次,也就是一笔画。

用二进制表示当前所有点的链接情况,已经连上的记为1,反之为0.

当前状态为dp[i][j]表示,i的点链接情况下,j为最后一个链接点的最小权值和。为什么将最后一个链接点记为状态?因为这样方便转移 ,可以转移到上一个点的状态里。

注意,转移的时候,j已经是最后链接的点了,那么j必须是为1的,且待转移的之前状态中j不能为1。

#include
using namespace std;
#pragma warning(disable:4996);
//#define int long long 
#define endl '\n'
#define rep(j,b,e) for(int j=(b);j<=(e);j++)
#define drep(j,e,b) for(int j=(e);j>=(b);j--)
const int N = 2e5 + 10;
int mod = (1e7) + 7;
int T = 1;
int n, m, x, k;
int a[100][100];
int dp[2000000][21];//走过i个点的情况(二进制表示1<> n;
	rep(j, 0, n - 1) {
		rep(i, 0, n - 1) {
			cin >> a[j][i];
		}
	}
	memset(dp, 0x3f, sizeof(dp));
	dp[1][0] = 0;
	//用二进制表示所有点是否走过
	rep(j, 0, (1 << n) - 1) {//枚举当前的所有状态,只要合法就能转移
		rep(i, 0, n - 1) {//枚举最后走的点
			if (j >> i & 1) {//如果在这个状态下这个点走过,才有意义
				rep(k, 0, n - 1) {//枚举待转移的点
					int preSt = (j - (1 << i));//i走过,但是因为i为最后走过的点,能转移过来的状态都不经过i
					if (k != i and preSt >> k & 1) {//待转移的点也得是走过的,而且不包括最后走的点i
						dp[j][i] = min(dp[j][i], dp[preSt][k] + a[k][i]);
					}
				}
			}
		}
	}
	cout << dp[(1 << n) - 1][n - 1];
}

你可能感兴趣的:(动态规划,状态压缩,算法)