状压DP简单来说就是用二进制的方式来压缩状态
具体的题目会有不同的表示方法
蒙德里安的梦想
首先分析这个问题,我们现在行上放小方格,在行上放完之后,那么纵向的小方格就只有一种放的方式。因此!只需要求出横向放的方式就可以了
状态表示:f[i][j]为数量,i表示枚举到第i列,j表示上一列伸出来的多少个小方格,j是用二进制表示的,为1的位表示当前位有小方格伸出来
对于j这一维度的状态表示就是状压DP
接下来再想一下状态转移
首先就是不能存在冲突,就是说第i列和第i-1列不能同时伸出一个小方格,这里可以用与(&)运算来判断结果是不是0
其次需要保证解决完每一行伸出来的小方格之后在当前列不能有长度是计数的空格,因为还需要放纵向的方块。这里可以用或(|)运算判断是否存在连续奇数个0即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=15,M=1<<N;
int n,m;
ll f[N][M];
bool st[M];
int main()
{
while(cin>>n>>m&&n!=0&&m!=0)
{
memset(f,0,sizeof(f));
//预处理过程,处理出当前过程是否满足放完横向方块后纵向不存在奇数个连续0
for(int i=0;i<1<<n;i++)
{
st[i]=1;
int cnt=0;
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
if(cnt&1) st[i]=0;
cnt=0;
}
else cnt++;
}
if(cnt&1) st[i]=0;
}
//DP过程
f[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((j&k)==0&&st[j|k]) f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl;
}
return 0;
}
最短hamilton路径
状态表示为:f[i][j]表示最小值,j表示总0号点走到j号点,i表示从0号点走到j号点经过的点,i用二进制进行表示,这一位是1表示经过了这个点,是0表示没有经过这个点
i这一维就是状态压缩
状态转移:就挺常规的了,就是和最短路算法相同,先便利当前状态经历过的所有点到目标点的距离,再在这些距离中取一个最小值即可
#include <bits/stdc++.h>
using namespace std;
const int N=21,M=1<<N;
int n;
int g[N][N];
int f[M][N];
int main()
{
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>g[i][j];
memset(f,0x3f,sizeof(f));
f[1][0]=0; //0号点到0号点自己的距离是0
for(int i=0;i<1<<n;i++)
for(int j=0;j<n;j++)
if(i>>j&1)
for(int k=0;k<n;k++)
if(i>>k&1)
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+g[k][j]);
cout<<f[(1<<n)-1][n-1]<<endl;
return 0;
}