01背包之第K优解问题详解

求前k优解的基本思想是将两个状态都表示成有序队列,将状态方程中的max/min转化成有序队列的合并。现在不太清楚这一句话没有关系,看以下分析:

首先,作为01背包问题的一个提升,弄懂第K优解问题之前需要明白01背包问题。这里就不再重述,直接给出01背包问题的动态转换方程。

                                                               f[i,v]=max(f[i-1,v],f[i-1,v-ci]+wi)

 

上式为未进行空间复杂度优化的01背包问题的状态方程,其含义是,将前i件物品放入容量为v的背包的最优解,等于不放当前第i件物品的最优解和放入当前第i件的最优解中的较大者。进行空间复杂度的优化后,方程变为:

                                                                f[v]=max(f[v],f[v-ci]+wi)

 

此时,需要使用倒序循环背包体积方可正确遍历。

通过状态方程知道f[v]表示选择前i件物品到体积为v的背包的最优价值,而当前遍历的体积v的最优价值f[v]是由f[v]与f[v-ci]+wi构成的。因此将前i件物品放入背包容量为v的背包中的最优价值的关键就在于,第i件物品是否应该放入背包中。再此基础上,可以将状态方程分为两部分,也就是两个状态,一个表示不放入第i件物品的情况下背包v的最优解;另一种是放入第i件物品的情况下背包v的最优解。即
                                                                                    a[v]=f[v];

                                                                                    b[v]=f[v-ci]+wi;

a和b序列就表示这两个状态,正常情况下a和b的维度应该和当前背包的体积一致,但是我们不需要这样做,继续分析。

将状态方程分为两部分(两个状态)是我们分析求最优值的时候的关键,我们在此基础上进行倒推,试想如果仅选择前i件物品放入背包容量为v的背包中可以获得当前背包容量的最优价值,那么当前背包容量v的次优解是否应该就是前i-1件物品放入背包容量为v-ci的背包中的价值,再加上放入第i件物品的价值就构成了当前背包容量的次优解呢?仔细思考之后,答案是肯定的。经过这一条分析,我们知道当前背包容量的k优解应该表示为一个大小对k的有序队列,即在原来的状态方程中再加上一维f[v][1..K],表示将前i件物品放入背包容量为v的背包中的前k优解。从而上面分成的两个状态也应该加一维,表示的意思分别为不选当前物品i的前k优解,和选择当前物品i的前k优解。即

                                                                                           a[1..k]=f[v][1..k]

                                                                                          b[1..k]=f[v-ci][1..k]+wi;

可以看到,实际上我们只需要给a与b两个状态k维即可,毕竟他们只是两个临时状态,我们最后求的还是背包问题的前k优解。这样做的好处是,我们将这两个临时状态让他们形成了两个有序队列,即a[1],b[1]分别是背包容量为v时两个状态的最优解,此时我们为了得到背包容量为v的时候,最终的最优解,则只需要取这两个状态最优解的较大者就可以了!(这是合并的重点)

取了最优值,如何取当背包容量为v是的次优解呢?首先取过的不能再取了,我们假设上一步取的是b[1],即选择前i件物品时,背包v能达到最优值。接下来我们应该比较a[1]与b[2]选择其中的较大者来作为当背包容量为v时的次优解了,为什么需要这样做呢?其表示的就是不选第i件物品的最优解与选择第i件物品的次优解做比较,最终结果作为背包容量为v时前i件物品的次优解。以此类推便可以得到第三优解第四优解……

最后给出一道例题代码,结合理解。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MIN -0xffffff
int main(){
	int N,V,K;
	scanf("%d%d%d",&K,&V,&N);
	int c,w,i,j,k,a[K+2],b[K+2],**dp;
	dp=(int**)malloc((V+1)*sizeof(int*));
	for(i=0;i<=V;i++){
		dp[i]=(int*)malloc((K+1)*sizeof(int));
		memset(dp[i],0,(K+1)*sizeof(int));
	}
	for(i=0;i<=V;i++){//有的题目要求要恰好填满背包的最优值,因此除了dp[0][0],其它都要赋值为负无穷 
		for(j=0;j<=K;j++){
			dp[i][j]=MIN;
		}
	}
	dp[0][1]=0;
	a[0]=b[0]=0;
	for(i=1;i<=N;i++){
		scanf("%d%d",&c,&w);
		for(j=V;j>=c;j--){
		for(k=1;k<=K;k++){//将状态分为两个临时状态,求解K优解的循环要写在背包容量内 
			a[k]=dp[j][k];
			b[k]=dp[j-c][k]+w;
		}
		int x,y,z;
		x=y=z=1;
		a[k]=b[k]=-1;//防止越界 
		while(z<=K && (a[x]!=-1||b[x]!=-1)){//将两个有序队列,从最优解排到次优解的队列合并 
			if(a[x]>b[y]){
				dp[j][z]=a[x++];
			}else{
				dp[j][z]=b[y++];
			}
			z++;//上面的判断就已经确定了第z优解,因此这里要递增,下次确定第z+1优解 
		}
		}
	}
	int ans=0;
	for(j=1;j<=K;j++)
	ans+=dp[V][j];
	printf("%d",ans);
}

欢迎留言评论以及交流,如有不足,请及时指出!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值