P2170 选学霸
题意
多人背包
题目描述
求01背包前k优解的价值和
DD 和好朋友们要去爬山啦!
他们一共有 K 个人,每个人都会背一个包。这些包 的容量是相同的,都是 V。可以装进背包里的一共有 N 种物品,每种物品都有 给定的体积和价值。
在 DD 看来,合理的背包安排方案是这样的: 每个人背包里装的物品的总体积恰等于包的容量。 每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。
任意两个人,他们包里的物品清单不能完全相同。 在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?
输入格式
第一行三个数K、V、N
接下来每行两个数,表示体积和价值
输出格式
前k优解的价值和
样例 #1
样例输入 #1
2 10 5 3 12 7 20 2 4 5 6 1 1
样例输出 #1
57
提示
对于100%的数据, K ≤ 50 , V ≤ 5000 , N ≤ 200 K\le 50,V\le 5000,N\le 200 K≤50,V≤5000,N≤200
tags
01背包变形,第k优解
思路
首先需要明确
- 一个正确的状态转移方程的求解过程遍历了所有可用的策略,也就覆盖了问题的所有方案。只不过由于是求最优解,所以其它在任何一个策略上达不到最优的方案都被忽略了。
- 对于第1优解,第2优解·····第k优解,它们是单调有序的
然后分析一下01背包的过程(v是体积,w是价值)
- 状态转移方程
dp[j]=max(dp[j],dp[j-v[i]]+w[i])
- 上述dp[j]与dp[j-v[i]]+w[i]为容量为j时的最优解与次优解(不分顺序),我们求普通01背包时只想要各阶段的最优解,因此会舍去次优解
- 但我们现在要得到前k优解,就不能忽略次优解了,而应用数组存起来
如何实现
dp[j][k]
表示:容量为j时的第k优解- 根据前面分析,它们是有序的,
dp[j][1]
即是最优解 - 好好理解一下这里增加一维的作用,它表示dp[j]这个点有k优解(有序的),即有k个元素,是一个有序序列
- 我们状态比较还是dp[j]与dp[j-v[i]]这两个有序序列,并合并两个序列的前k优解
- 如何合并?
- 可以像归并排序一样
- 在两个序列中分别放一个指针l与r(双指针),比较
dp[j][r]与dp[j-v[i]][l]+w[i]
若哪个大取哪个值,并将其指针后移,直到取到k个元素,用x[k]来记录一下,最后更新dp[j][k]
初始化问题
dp[0][1]
设置为0,只有最优解且为0- 其他设置为负无穷,因为前0个物品除了能装满体积为0的背包且最优解为0,其他情况没有解(因为要装满背包,满背包问题),要设置为负无穷
AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxk=55,maxn=205,maxv=5e3+5;
int k,v,n;
int a[maxn],b[maxn],c[maxk];
int dp[maxv][maxk];
int main(){
cin>>k>>v>>n;
for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
memset(dp,0xcf,sizeof(dp));
dp[0][1]=0;
for(int i=1;i<=n;i++){
for(int j=v;j>=a[i];j--){
int add=0,l=1,r=1;
while(add<k){
if(dp[j][l]<dp[j-a[i]][r]+b[i]){
c[++add]=dp[j-a[i]][r]+b[i];
r++;
}
else {
c[++add]=dp[j][l];
l++;
}
}
for(int x=1;x<=k;x++)dp[j][x]=c[x];
}
}
int ans=0;
for(int i=1;i<=k;i++)ans+=dp[v][i];
cout<<ans<<endl;
}
可以看看背包九讲中前k优解的分析