【海亮集训DAY12(三)】对背包的补充二的补充——第k优解


专辑:海量集训-动态规划-背包


分三次更新


对第三次更新的补充


对于上次的更新,由于我还没将第k优解的算法研究透,所以就暂时没有写,在这里不上


背包的第k优解

第k优解,顾名思义,就是让我们求出一个背包问题的第k优解(第k优解是什么想必不需要解释了吧)

分析:如果相应的最优解问题能写出状态转移方程、用动态规划解决,那么第K 优解则比求最优解的复杂度上多一个系数K 即可。
那我们还是以01背包为例,状态转移方程为:

f [ i ] [ j ] = m a x { f [ i 1 ] [ j ]  ① f [ i 1 ] [ j w [ i ] ] + c [ i ] ,  ②

如果要求第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;
}

猜你喜欢

转载自blog.csdn.net/huang_ke_hai/article/details/81086862