hdu 2639 Bone Collector II (dp 01背包求第k优解)

题目:点击打开链接


题目大意:

有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;
}


你可能感兴趣的:(Collector)