题目:点击打开链接
题目大意:
有n件物品,每件物品有体积和价值两个属性, 一个小偷带着一个大小为v的背包,要偷这些东西,问小偷能偷的第k大的价值是多少?
思路:
这题和典型的01背包求最优解不同,是要求第k大的解,所以,最直观的想法就是在01背包的基础上再增加一维,用来保存前k大小的数,然后在递推时,根据前一个状态的前k
大小的数推出下一个阶段的前k个数保存下来。
d[i][j][k], 表示取前i个物品,用j的费用,第k大价值是多少
在递推d[i][j][1...k]时,先获取上一个状态d[i-1][j][1...k]递推出来所有的值:
即集合A={dp[i-1][j][p]+w[i], 1<=p<=k}, 还有原来的值集合B={dp[i-1][j][p], 1<=p<=k}
然后把集合A和B中的前k大的值按从大到小顺序赋值给d[i][j][1...k]
这一步骤可以用归并排序中的合并方法,因为集合A和B一定是按照从大到小的顺序排列的。代码:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<vector> #define SQ(x) ((x)*(x)) #define MP make_pair const int INF = 0x3f3f3f3f; const double PI = acos(-1.0); typedef long long int64; using namespace std; const int MAXN = 110; int n, volume, z, k; int c[MAXN], w[MAXN]; int f[1100][35]; int a[35], b[35]; int main(){ int nCase; scanf("%d", &nCase); while(nCase--){ scanf("%d%d%d", &n, &volume, &k); for(int i=1; i<=n; ++i) scanf("%d", &w[i]); for(int i=1; i<=n; ++i) scanf("%d", &c[i]); memset(f, 0, sizeof(f)); for(int i=1; i<=n; ++i){ for(int v=volume; v>=c[i]; --v){ int p1=0, p2=0; for(int j=1; j<=k; ++j){ if(f[v][j]) b[p2++] = f[v][j]; a[p1++] = f[v-c[i]][j]+w[i]; } // 用归并排序的合并方法 int x=0, y=0, j=1; while(j<=k && (x<p1 || y<p2)){ int tmp=0; if(y>=p2 || x<p1 && a[x]>b[y]){ tmp = a[x++]; }else tmp = b[y++]; if(j==1 || tmp!=f[v][j-1]) //不能相同 f[v][j++] = tmp; } } } printf("%d\n", f[volume][k]); } return 0; }