背包第K优解问题
(这个东西其实早就应该会的,鬼知道怎么就拖到现在。一位学弟说自己写了一道背包k有解的题,想了想突然发现自己不太会,我果然好菜啊……)
- 写这篇博客的目的不是说A了这道题来讲思路,那样没什么用。毕竟这是提高甚至普及知识点,没什么可吹嘘的。写此文的目的在于总结第k优解这类问题,下面我们来深入讨论一下。
- 当然,我们仍然以01背包为例。我们在求01背包最优解的时候,有这样一个式子, f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]) f[i][j]=max(f[i−1][j],f[i−1][j−v[i]]+w[i])。他们两个里面一个会是当前状态 f [ i ] [ j ] f[i][j] f[i][j]的最优解和次优解。因为目前对这个状态有用的决策只有两个,那么肯定一个是最优解一个是次优解。
- 推广到第k优解呢?我们把状态拓展一维, f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示到第i个物品,使用j的空间的第k优解。对于我们来说,有用的状态还是 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]和 f [ i − 1 ] [ j − v [ i ] ] f[i-1][j-v[i]] f[i−1][j−v[i]],只不过他们此时不再是一个单值了,而是一个有序的序列,对,你把它当成一个数组。我们要求当前状态的前K优解,其实就是从这两个序列里选出k个最优的。那么明显可以用一个双指针,比较加进去就好了。
- 细节问题在于初始化。最开始我以为反正不会出现负数的情况,直接默认为0,不管不就好了?可是仔细考虑一下f数组的定义,当i=0的时候,只有 f [ 0 ] [ 0 ] [ 1 ] f[0][0][1] f[0][0][1]是合法的状态,其他的都不合法。所以我们需要把f数组初始化为负无穷,使 f [ 0 ] [ 0 ] [ 1 ] = 0 f[0][0][1]=0 f[0][0][1]=0, n ∗ k ∗ v n*k*v n∗k∗v递推即可。
#include<bits/stdc++.h>
using namespace std;
int n,m,k,f[5010][51],v[210],w[210],c[51];
int main(){
scanf("%d%d%d",&k,&m,&n);
for(int i=1;i<=n;++i) scanf("%d%d",&v[i],&w[i]);
memset(f,0xcf,sizeof(f));
f[0][1]=0;
for(int i=1;i<=n;++i){
for(int j=m;j>=v[i];--j){
int l=1,r=1,t=0,tmp=0;
while(t<k){
if(f[j][l]<f[j-v[i]][r]+w[i])
c[++tmp]=f[j-v[i]][r]+w[i],r++;
else c[++tmp]=f[j][l],l++;
t++;
}
for(int o=1;o<=k;++o) f[j][o]=c[o];
}
}int ans=0;
for(int i=1;i<=k;++i) ans+=f[m][i];
cout<<ans<<endl;
return 0;
}