目录
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<
本题的主要思路是,首先发现,要想满满当当的覆盖大矩形,那么横着摆放的长方形确定了,竖着摆放的也会确定。,此时横着摆放完的空白行在每一列必定是长度为偶数的。
所以去求横着摆放,且满足条件的状态有多少种就行。
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不应该用突出来的
}
}
和最小生成树有点像,但区别是此时只需要一条路径,每个点只经过一次,也就是一笔画。
用二进制表示当前所有点的链接情况,已经连上的记为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];
}