hdu 2639 Bone Collector II 01背包K优解(不同策略相同权值视为相同)

缘起

崔天翼神犇的《背包九讲2.0》 9.5节讲解了背包问题K优解问题的解法. 崔神犇总结到

1
2
对于求次优解、第K优解类的问题,如果相应的最优解问题能写出状态转移方程、用动态规划解决,
那么求次优解往往可以相同的复杂度解决,第K优解则比求最优解的复杂度上多一个系数K。

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
给出一行价值,一行体积,让你在v体积的范围内找出(严格,也就是1122345667,则3是第三小而不是第5小)第k小的值

【输入】
多样例. 第一行是样例数. 每个样例三行, 第一行是 n,v,k,N <= 100 , V <= 1000 , K <= 30
表示骨头的数量、背包的容量、以及k. 然后一行n个整数分别是每个骨头的价值. 最后一行是n个整数是骨头的体积

【输出】
k优解

【样例输入】
3
5 10 2
1 2 3 4 5
5 4 3 2 1
5 10 12
1 2 3 4 5
5 4 3 2 1
5 10 16
1 2 3 4 5
5 4 3 2 1

【样例输出】
12
2
0

【限制】
2s

关于01背包问题的K优解的思路《背包九讲2.0》 9.5节已经讲得很清楚了. 这里不再重复, 究其根本其实就是将dp[i][j] 由原先的最优方案改成一个大小为K的有序队列. 然后状态转移的时候, 原先的 max 操作变成了O(K)时间合并两个K长有序队列为一个新的长K队列. 所以算法的复杂度变成 O(NVK).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//#include "stdafx.h"
#include <stdio.h>
#include <string.h>
//#define LOCAL
const int maxn = 105, maxv = 1005, maxk = 35;
int n,v,k,a[maxn],b[maxn], dp[maxv][maxk],tmp[maxn]; // dp[j][1,..dp[j][0]]是仅仅考虑前几个骨头, 容量为j的时候, 最优的<=k个解(他们严格递减), 即dp[j][0]是个数

void zo(int i) // K优解的01背包, 其实就是在标准的01背包改成维护队列
{
for (int j = v; j>=b[i]; j--)
{
int len1 = dp[j][0];
int len2 = dp[j-b[i]][0];// 归并的两个队列的长度, 参与归并的严格递减队列是dp[j][1,...,len1]和dp[j-b[i]][1,..,len2]
int len = 0,x=1,y=1;
while(x<=len1 && y<=len2 && len<=k) // 注意这里的len<=k, 因为只需要维护k长的严格递减队列就行了
{
if (dp[j][x]>dp[j-b[i]][y]+a[i])
{
tmp[len++] = dp[j][x++];
}
else if (dp[j][x]<dp[j-b[i]][y]+a[i])
{
tmp[len++] = dp[j-b[i]][y++]+a[i];
}
else
{
tmp[len++] = dp[j][x++];
y++; // 这里体现了不同策略权值相同视为相同
}
}
while(x<=len1 && len<=k)
{
tmp[len++] = dp[j][x++];
}
while(y<=len2 && len<=k)
{
tmp[len++] = dp[j-b[i]][y++]+a[i];
} // 标准的归并
memcpy(dp[j]+1, tmp, len*sizeof(int));
dp[j][0] = len;
}
}

int main()
{
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
// freopen("d:\\my.out", "w", stdout);
#endif
int kase;
scanf("%d",&kase);
while (kase--)
{
scanf("%d%d%d", &n,&v,&k);
for (int i = 1;i<=n;i++)
{
scanf("%d", a+i);
} // 价值
for (int i = 1;i<=n;i++)
{
scanf("%d", b+i);
} // 体积
memset(dp,0,sizeof(dp));
for (int j = 0;j<=v;j++)
{
dp[j][0] = 1;
dp[j][1] = 0;
} // 不考虑任何物品的取用的时候, 任何容量限制下, 最优解都是0, 因为本题要求的队列是严格递减的,所以只有1个0
for (int i = 1;i<=n;i++)
{
zo(i);
}
printf("%d\n", dp[v][k]);
}
return 0;
}

ac情况

Status Accepted
Time 93ms
Memory 1876kB
Length 1564
Lang C++
Submitted 2019-10-27 22:02:37
Shared
RemoteRunId 31142224