专辑:海量集训-动态规划-背包
分三次更新
对第三次更新的补充
对于上次的更新,由于我还没将第k优解的算法研究透,所以就暂时没有写,在这里不上
背包的第k优解
第k优解,顾名思义,就是让我们求出一个背包问题的第k优解(第k优解是什么想必不需要解释了吧)
分析:如果相应的最优解问题能写出状态转移方程、用动态规划解决,那么第K 优解则比求最优解的复杂度上多一个系数K 即可。
那我们还是以01背包为例,状态转移方程为:
如果要求第k优解,那么状态f[i][c]就应该是一个大小为k的数组f[i][c][k+1]。其中f[i][c][k]表示前i个物品、背包大小为c时,第k优解的值。显然可以认为f[i][c][k+1]是一个有序队列。
然后可以说:f[i][c]这个有序队列是有①、②这两个序列合并得到的。有序队列①即f[i-1][c][k],②则理解为在f[i-1][c-w[i]][k]的每个数上加上v[i]后得到的有序队列。合并这两个队列并将结果前k项存到f[i][c][k+1]中的时间复杂度是O(K)。最后的答案是f[n][c][k]。总时间复杂度是O(cnk)。
实际上,一个正确的状态转移方程求解过程已经覆盖了问题的所有方案。只不过由于是求最优解,所以其他达不到的最优的方案都被忽略了。因此,上面的做法是正确的。
另外,要注意题目对“第K优解”的定义,将策略不同但权值相同的两个方案是看作是同一个解还是不同的解。如果是前者,则维护有序队列是要保证队列里的数没有重复的。
/*a数组表示取取当前物品的第k优解,b数组表示不取当前物品的第k优解
dp[0][1]=0;//初值
for (int i=1;i<=n;i++){//枚举物品
for (int j=v;j>=c[i];j--){//体积
int l=1,r=1;//l为a数组的指针,r为b数组的指针
for (int p=1;p<=k;p++){//枚举k种解
a[p]=dp[j-c[i]][p]+w[i]; //取
b[p]=dp[j][p];//不取
if (a[l]>=b[r]) dp[j][p]=a[l++];
else dp[j][p]=b[r++];//去一个较大值
}
}
}
第k优解的代码不是很长,但主要是理解,只要理解透,代码实现也不是那么难。
那么接下来就来一道是模板题的模板题
题目描述:DD 和好朋友们要去爬山啦!他们一共有 K 个人,每个人都会背一个包。这些包的容量是相同的,都是 V。可以装进背包里的一共有 N 种物品,每种物品都有给定的体积和价值。
在 DD 看来,合理的背包安排方案是这样的:
1.每个人背包里装的物品的总体积恰等于包的容量。
2.每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。
3.任意两个人,他们包里的物品清单不能完全相同。
在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?
分析:裸的第k优解的例题,套模板上去即可
不过不同的是,这道题并不是单纯的求第k优解,而是求前k优解的和,只需要在最后套一重循环累加即可
具体代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,v,k;
int a[100001]={};//不取
int b[100001]={};//取
int dp[5001][5001]={};//dp[i][j]表示填满i的第j优解
int w[10001]={};//价值
int c[10001]={};//体积
int main(){
scanf("%d %d %d",&k,&v,&n);
for (int i=1;i<=n;i++) scanf("%d%d",&c[i],&w[i]);
memset(dp,-10,sizeof(dp));
dp[0][1]=0;
for (int i=1;i<=n;i++){
for (int j=v;j>=c[i];j--){
int l=1,r=1;
for (int p=1;p<=k;p++){
a[p]=dp[j-c[i]][p]+w[i];
b[p]=dp[j][p];
if (a[l]>=b[r]) dp[j][p]=a[l++];
else dp[j][p]=b[r++];
}
}
}//模板不解释
int ans=0;
for (int i=1;i<=k;i++) ans+=dp[v][i];//累加前k优解的和
cout<<ans;
return 0;
}