前言:

昨日学习状态压缩dp学的我快疯掉了,第一次接触状压dp雀氏第一时间很难接受。

so写一篇博客加以总结

概述:

状态压缩不同于线性dp区间dp等,经常用二进制数表示某一种状态。

下面就让两道题带你了解状压dp的魅力。

实践:

题目一:蒙德里安的梦想

求把 NM的棋盘分割成若干12的长方形,有多少种方案

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<bits/stdc++.h>
using namespace std;
const int N=12,M=1<<N;
long long dp[N][M];//建立动态规划数组
bool st[M];
int main()
{
int n,m;
while(cin>>n>>m,n|m){//读入数据
for(int i=0;i<1<<n;i++){
st[i]=true;//先初始化该二进制是否合法,即是否符合摆放条件
int cnt=0;//统计该数字有几个0
for(int j=0;j<n;j++){
if(i>>j&1){//不断右移数字,计数0个数
if(cnt&1){//若有奇数个0不符合摆放条件
st[i]=false;
break;
}
}
else cnt++;
}
if(cnt&1)st[i]=false;//特殊处理高位0的情况
}
memset(dp,0,sizeof dp);//初始化数组
dp[0][0]=1;//考虑特殊情况
for(int i=1;i<=m;i++){
for(int j=0;j<1<<n;j++){
for(int k=0;k<1<<n;k++){
if(st[j|k]&&(j&k)==0){//只判断前一列和后一列摆放合法,同时前后不能有重叠部分
dp[i][j]+=dp[i-1][k];
}
}
}
}
cout<<dp[m][0]<<endl;//最后一列只能竖放,同时二进制数肯定为0000,则最终答案为dp[m][n];
}
return 0;
}

题目二:最短hamilton路径

给定一个n个点的带权无向图,点从0~n-1标号,求起点为0到终点n-1的最短Hamilton路径

Hamilton路径的定义是从0到n-不重不漏地经过每一个点恰好为1

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
using namespace std;
const int N=21,M=1<<N;
int w[N][N],dp[M][N];//M表示二进制数。N表示当前为什么点
int main(){
int n;
cin>>n;S
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>w[i][j];
}
}
memset(dp,0x3f,sizeof dp);
dp[1][0]=0;
for(int i=0;i<1<<n;i++){
if(i&1){//因为题目要求从0这个点开始走,所以二进制最后一个数一定为1
for (int j = 0; j < n; j++) {
if (i >> j & 1) {//保证i的二进制数中为数字为1时起跳,这样才合法
for (int k = 0; k < n; k++) {//枚举跳到j之前的k点
if ((i - (1 << j)) >> k & 1) {
dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + w[k][j]);
}//去掉j这个点,同时找到合法的起跳点k
}
}
}
}
}
cout<<dp[(1<<n)-1][n-1];//最终答案肯定在二进制数全为1的点,且最终点为n-1;
return 0;
}