参考了一个博主的文章,他用的是背包问题来讲解的状压dp,我个人感觉讲的很不错,所以就想写一遍文章来浅谈感受和学习总结,算是入门,因为以前一直感觉状压dp很难,一直理解不透,看完这篇文章确实感觉理解的更好啦。
背包问题可能大家都不陌生,今天就用背包讲一下状压dp原理,首先dp就要
1.先定义状态:
那么考虑到n个物品,只有两种状态选与不选,所以二进制数0和1足以表示状态集,因为如果开个n为数组的话,这样对于空间的浪费是很严重的,如果n个都选就是就是 1111…n个1,都不选就是n个0,到这里大家可以看到其实空间其实已经压缩到一个维度啦,这个过程就叫做状态压缩。
2.状态转移方程:
这个相信大家都可想到啦,那就是比如说dp[10000]只能是dp[00000]+W[1],诸如此类大家应该可以理解。
3.求解问题
代码如下:
#include<stdio.h>
const int INF = 1 << 15;
int dp[INF + 10];
int dp1[INF+ 10] ; //定义状态
void print1(int num); //打印在状态为num的时候的所有物品放与不放的情况
/*
3 6
2 5
3 8
4 9 //一组样例
0 0 0
1 5 2
01 8 3
11 13 5
001 9 4
101 14 6
011 17 7
111 22 9
*/
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
int W[20],C[20];
for(int i = 0; i < n; i++)
scanf("%d %d",&C[i],&W[i]) ;
int res = -1;
for(int i = 0; i < (1 << n); i++)
{
for(int j = 0; j < n; j++)
{
if(!(i&(1 << j))) //状态转移 这个代表第j为0
{
int temp = i | (1<<j);//将第j为变为0
dp[temp] = dp[i] + W[j];
dp1[temp] = dp1[i] + C[j];
}
}
}
for(int i = 0; i < (1 << n); i++)
{
print1(i);
printf("\t%d\t%d\n",dp[i],dp1[i]); //打印出每种方案的情况,价值 和耗费的空间
}
}
return 0;
}
void print1(int num)
{
int k= 0;
if(num == 0)
printf("0");
for(;(1 << k) <= num; k++)
{
if(num & (1<<k))
printf("1");
else
printf("0");
}
}
想到这里我猜大家应该可以理解一点状压dp啦,那么稍微来一道需要点思维的状压dp:
例如:最小总代价(Vijos-1456)
题目描述:
n个人在做传递物品的游戏,编号为1-n。
游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。
即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;
求当物品经过所有n个人后,整个过程的总代价是多少。
输入格式:
第一行为n,表示共有n个人(16>=n>=2);
以下为n*n的矩阵,第i+1行、第j列表示物品从编号为i的人传递到编号为j的人所花费的代价,特别的有第i+1行、第i列为-1(因为物品不能自己传给自己),其他数据均为正整数(<=10000)。
(对于50%的数据,n<=11)。
输出格式:
一个数,为最小的代价总和。
输入样例:
2
-1 9794
2724 –1
输出样例:
2724
算法分析:看到2<=n<=16,应想到此题和状态压缩dp有关。每个人只能够被传递一次,因此使用一个n位二进制数state来表示每个人是否已经被访问过了。但这还不够,因为从这样的状态中,并不能清楚地知道现在物品在谁 的手中,因此,需要在此基础上再增加一个状态now,表示物品在谁的手上。
所以二维状压dp就可以解决啦。
假设dp[state][now] state代表当前n个人的状态集,now代表目前在哪个人手中,
所以dp[state][now]=min(dp[state][now],dp[pre][t]+e[now][t])
代表从now->t的转移方程
【代码分析]
#include <cstdio>
#include <iostream>
#include<cstring>
using namespace std;
int n,a[20][20],dp[1<<16][20],ans=0x3f3f3f3f;
int main()
{
scanf("%d",&n);
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
scanf("%d",&a[i][j]);
}
}
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int i=0; i<n; i++)
{
dp[1<<i][i]=0;
}
for(int i=0; i<1<<n; i++)
{
for(int j=0; j<n; j++)
{
if(dp[i][j]!=0x3f3f3f3f)
{
for(int k=0; k<n; k++)
{
if(!(i&(1<<k)))//代表下一位k是零 枚举将其变为1
{
dp[i|(1<<k)][k]=min(dp[i|(1<<k)][k],dp[i][j]+a[j][k]);
}
}
}
}
}
for(int i=0; i<n; i++)
{
ans=min(ans,dp[(1<<n)-1][i]);
}
printf("%d",ans);
}
这里稍微涉及到的一些位运算规则大家可以参考这个大佬的文章:
位运算规则