P2170 选学霸[01背包变形,前k优解]

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 K50,V5000,N200


tags

01背包变形,第k优解

思路

首先需要明确

  1. 一个正确的状态转移方程的求解过程遍历了所有可用的策略,也就覆盖了问题的所有方案。只不过由于是求最优解,所以其它在任何一个策略上达不到最优的方案都被忽略了。
  2. 对于第1优解,第2优解·····第k优解,它们是单调有序的

然后分析一下01背包的过程(v是体积,w是价值)

  1. 状态转移方程dp[j]=max(dp[j],dp[j-v[i]]+w[i])
  2. 上述dp[j]与dp[j-v[i]]+w[i]为容量为j时的最优解与次优解(不分顺序),我们求普通01背包时只想要各阶段的最优解,因此会舍去次优解
  3. 但我们现在要得到前k优解,就不能忽略次优解了,而应用数组存起来

如何实现

  1. dp[j][k]表示:容量为j时的第k优解
  2. 根据前面分析,它们是有序的dp[j][1]即是最优解
  3. 好好理解一下这里增加一维的作用,它表示dp[j]这个点有k优解(有序的),即有k个元素,是一个有序序列
  4. 我们状态比较还是dp[j]与dp[j-v[i]]这两个有序序列,并合并两个序列的前k优解
  5. 如何合并?
    1. 可以像归并排序一样
    2. 在两个序列中分别放一个指针l与r(双指针),比较dp[j][r]与dp[j-v[i]][l]+w[i]若哪个大取哪个值,并将其指针后移,直到取到k个元素,用x[k]来记录一下,最后更新dp[j][k]

初始化问题

  1. dp[0][1]设置为0,只有最优解且为0
  2. 其他设置为负无穷,因为前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优解的分析

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值