背包问题-动态规划-理论篇

01背包

现在有n个物品,每个物品最多取一次, 有一个背包,它所装物品重量之和不能超过W,第i个物品它的重量是wi,价值是vi, 现在的问题是,在不超过背包容量的限制下,最多能获得的物品价值之和是多少。
(1<=n<= 2000, 0<= W<= 2000, vi>0, wi> 0)
解决方案1
我们先考虑遍历所有的方法,如果物品个数为n,那么总的方案数就是2 ^ n种(选或者不选),我们可以用dfs搜索实现。

//递归的解决的代码,需要用记忆化搜索
int dfs(int i, int j)
{
	if(i==0||j<0) return 0;
	if(dp[i][j]!=-1) return dp[i][j];
	dp[i][j]= dfs(i-1,j);
	if(j>=w[j])dp[i][j]=max(dp[i][j],dfs(i-1,j-w[i]));
	return dp[i][i]; 
}
/*初始化dp数组为-1, answer=dfs(n, W).
尝试把递归解决动态规划和搜索进行对比。*/

但是n最大能到达2000,显然2 ^ 2000的复杂度难以接受,我们能不能得到一种时间复杂度更小的方法呢?
解决方案2
我们考虑用dp[i][j]表示已经处理了前i个物品,现在背包里的物品重量之和不大于j的情况下所能获得的最大物品价值之和。
状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i- 1][j-wi]+vi)
dp[i-1][ij]:表示不选第i个物品。
dp[i- 1][j-wi]+vi:表示选第i个物品。

//递推解决
for(int i=0;i<=W;i++) dp[0][i]=0;
for(int i=1;i<=n;i++)
	for(int j=0;j<=W;j++)
	{
		d[i][j]= dp[i-1][j];
		if(j>=w[i])
			dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[j]);
	}
//answer=dp[n][W]

空间优化
使用滚动数组: cur表示当前层,last表示上一层

for(int j=0;j<=W;j++)cur[j]=0;
for(int i=1;i<=n;j++)
{
	for(int j=0;j<=W;j++)
		last[j]=cur[j],cur[j]=0;
	for(int j=0;j<=W;j++)
	{
		cur[j]=last[j];
		if(j>=w[i])
			cur[j]=max(cur[j],last[j-w[i]);
	}
}
// answer = cur[W]

无限背包

现在有n个物品,每个物品能取任意次,有一个背包,它所装物品重量之和不能超过W,第i个物品它的重量是wi,价值是vi, 现在的问题是,在不超过背包容量的限制下,最多能获得的物品价值之和是多少。
(1<=n<= 2000, 0<= W<= 2000, vi>0, wi> 0)
解决方案1
把无限背包问题转换成01背包问题,第i种物品最多有W / wi个起到作用,把每一种物品都拆成最多可能购买的数量,然后用01背包解决问题。最坏复杂度O(W* W * n)。
解决方案2
我们考虑用dp[i][j]表示已经处理了前i种物品,现在背包里的物品重量之和不大于j的情况下所能获得的最大物品价值之和。
状态转移方程:
dp[i][j]= max(dp[i-1[j], dp[i][j-wi]+vi)
dp[i-1][j]:表示不选第i种物品。
dp[i][j-wi]+vi :表示再选个第i种物品。

memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
	for(int	j=w[i];j<=W;j++)
		dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 
//answer=dp[W]

多重背包

现在有n个物品,每个物品能取ci次,有一个背包,它所装物品重量之和不能超过W,第i个物品它的重量是wi,价值是vi, 现在的问题是,在不超过背包容量的限制下,最多能获得的物品价值之和是多少。
(1 <=n<= 2000, 0<= W<= 2000, vi>0, wi> 0)
解决方案1
把第i种物品拆成ci个,然后直接01背包求答案。
复杂度O(n * ci *W)
解决方案2
利用二进制拆分:
把最多能取ci个的物品拆分成:1,2,4,8, 16… 2^n , k 。
其中n是最大的满足1 +2 +4 +8 +16+ … + 2^n <= ci 的数。
k =ci -1 -2 -4 -8 -16 -… -2 ^n。
例子:
对于ci==7的物品,我们对于这种物品一共有8种决策,分别是选0, 1, 2, 3, 4, 5, 6, 7个。法1:拆成7个数量为1的物品(0000000-1111111)进行01背包就能得到这8种决策。 法2:如果把7拆分成数量为1个,2个,4个的三个物品(000-111),用这三个物品进行01背包也能覆盖上面这8种决策。
对于ci==12的物品,拆分成数量为1个,2个,4个,4个的物品(0000-1111),用这四个物品进行01背包(12<16)能覆盖0-12这13种决策。

memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
	int num;
	for(num=1;num<=c[i];num*=2)
	{
		int cw=num*w[i];
		int cv=num*v[i];
		for(int j=W;j>=cw;j--)
			dp[j]=max(dp[j], dp[j-cw]+cv);
		c[i]-=num;
	}
	num=c[i];
	if(num)
	{
		int cw=num*w[i];
		int cv=num*v[i];
		for(int j=W;j>=cw;j--)
			dp[j]=max(dp[j],dp[j-cw]+cv);
	}
}

状态压缩dp

概念

我们有多个物品,每个物品都有一个状态,我们用一个数字就去表示出所有物品的状态,这个就是状态压缩。简而言之就是把多个物品的状态压缩成一个数字。
举例
我们有4个物品,每个物品有两种状态分别是有和没有,我们用0表示没有,用1表示有。

高~>低有的物品没有的物品
10101, 30, 2
01101, 20, 3
10110, 1, 32
对于一个压缩过后的十进制数S通过**(S>>i)& 1**获取到第i个物品的信息。

例如:S=10=1010 (二进制),想获取第1个物品的信息,(S>>1)=5=0101 (二进制),此时最低位就是一个物品的信息,所以(S>> 1) & 1就得到了第一个物品的信息。

简单例题

有n个城市,标号从0到n- 1,现在知道了每两个城市之间的距离,第i个城市和第j个城市之间的距离为d [i][j],现在有一个人想从0号城市开始把所有城市至少走一遍需要走多少距离。
(1 <=n<= 20, d[i][j]> 0)
解题思路
枚举所有的方案,只需要枚举1到n-1的全排列就可以,对于每一种方案计算一遍距离。时间复杂度O( (n- 1)! )
深入思考:对于所有经过城市相同,最后-一个到达城市也相同的情况,我们只需要保留距离最小的那个就可以了。
如果一共有5个城市,分别是0, 1, 2, 3, 4考虑这两种情况

0->1->3->2距离=10(1) 这条路径不需要扩展了
0->3->1->2距离= 9(2)

dp[i][mask] : 经过城市的情况是mask,最后停在i的最短距离
状态转移方程: dp[i][S] = max(dp[j][S^(1<<i)) 满足(S>>j& 1)== 1 &&j!= i
复杂度0 (n* (2 ^(n-1)))

for(int mask=0;mask<(1<<n);mask++)
	for(int i=0;i<n;i++)
		dp[j][mask]=inf;
dp[0][1]=0;
for(int mask=0;mask<(1<<n);mask++)
	for(int i=0;i<n;i++)
		if(mask>>i&1)
			for(int j=0;j<n;j++)
				if(j!=i&&mask>>j&1)
					dp[i][mask]=min(dp[i][mask],dp[j][mask^(1<<i)]);

位运算介绍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值