Programming Contest
Bob will participate in a programming contest. There are altogether n
problems in the contest. Unlike in PAT (Programming Ability Test), in a programming contest one can not obtain partial scores. For problem i
, Bob will need time[i]
to solve it and obtains the corresponding score[i]
, or he may choose not to solve it at all. Bob will be happy when he obtains a total score no less than happy_score
. You are supposed to find the minimum time needed for Bob to be happy. The function need_time
must return the minimum time, or -1
if it is impossible for Bob to obtain a score no less than happy_score
.
Format of function:
int need_time(const int time[], const int score[], int happy_score, int n);
Here n
(1≤n
≤ MAXN
) is the number of problems; happy_score
(1≤ happy_score
≤ MAXS
) is the minimum score for Bob to be happy; time[]
is the array to store time[i]
(1≤time[i]
≤100) which is the time to solve problem i
; score[]
is the array to store score[i]
(1≤score[i]
≤100) which is the score Bob gets for solving problem i
.
Sample program of judge:
#include <stdio.h>
#define MAXN 10
#define MAXS 1000
int need_time(const int time[], const int score[], int happy_score, int n);
int main() {
int n, i, happy_score;
int time[MAXN], score[MAXN];
scanf("%d %d", &n, &happy_score);
for (i = 0; i < n; ++i)
scanf("%d", &time[i]);
for (i = 0; i < n; ++i)
scanf("%d", &score[i]);
printf("%d\n", need_time(time, score, happy_score, n));
return 0;
}
/* Your function will be put here */
Sample Input:
6 121
84 87 78 16 94 38
87 93 50 22 63 28
Sample Output:
125
本题是0/1背包问题的变种,首先介绍0/1背包问题:
0/1背包问题背景
给定背包的承重W,一堆物品的重量weights[ ]和价值values[ ],求背包能装入物品的最大总value
0/1背包问题解法
定义一个二维数组 dp[物品总数][承重+1]
存储当前value,其中 dp[i][j] 表示:从前 i 件物品中找出一些放入背包,在总重不超过承重W的情况下,能达到的最大value。
第 i 件物品重量为 weight[i],价值为 value[i],根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
① 第 i 件物品的重量超过背包承重,即 weight[i] > j。此时就算拿出背包中的所有物品也无法放下,所以它不可能被放入背包,因此有:dp[i][j] = dp[i-1][j]
② 第 i 件物品可以添加到背包中(可能拿出一些已有物品)。此时也可以有放入与不放入两种情况,应取value更大的那种。
(2.1)若不放入,有:dp[i][j] = dp[i-1][j]
(2.2)若放入,情况变为从某个状态下加入第i件物品并达到dp[i][j]结果,可知前一状态时的背包承重应为 j-weight[i]。因此有:dp[i][j] = dp[i-1][ j-weight[i] ] + value[i]
。
综上,递推式为:
dp[i][j] = max( dp[i-1][j], max( dp[i-1][ j-weight[i] ] + value[i], dp[i-1][j] ) );
即 dp[i][j] = max( dp[i-1][ j-weight[i] ] + value[i], dp[i-1][j] );
dp数组的一维简化:
可以看到不管第 i 件物品有没有加到背包中,新一轮 i 下的dp值都只由 i-1 决定,并且这种决定对于 j 是单方向的(即较大的 j 处的值只和较小或相同的 j 的值有关),因此若 j 从W -> 0 递减,dp的数组可以依次覆盖。
0/1背包问题的代码:
int* dp = (int*)malloc((W+1)*sizeof(int));
for (int i = 0; i <= W; i++) dp[i] = 0;
for (int i = 0; i <= n; i++) { // n = 物品数
for (int j = W; j >= 1; j--) { // 逆序遍历
if (j >= weights[i]) {
dp[j] = max(dp[j], dp[ j-weights[i] ] + values[i] );
}
}
}
return dp[W];
回到原问题
本问题是求在 sum_score >= happy_score 情况下,sum_time 的最小值。
事实上:
- 做的题的score >= happy_score <=> 未做题的score < sum_score - happy_score
- 做的题的 (time) min <=> 未做题的 (time) max
所以原题等价于求出一些没做的题,使得,在这些题的总score不超过sum_score - happy_score情况下这些题的总time取得最大值。
所以:
- 未做题的总score即为物品的weights
- sum_score - happy_score即为背包的承重
- 未做题的总time即为物品的总value
这样我们完成了原问题与0/1背包问题的完美双射。
#include <stdlib.h>
#define max(x,y) ((x)>(y)?(x):(y))
int need_time(const int time[], const int score[], int happy_score, int n)
{
int sum_time = 0;
for (int i = 0; i < n; i++) sum_time += time[i];
int sum_score = 0;
for (int i = 0; i < n; i++) sum_score += score[i];
if (sum_score < happy_score) return -1;
int n_happy_score = sum_score - happy_score;
int* dp = (int*)malloc((n_happy_score+1) * sizeof(int));
for (int i = 0; i <= n_happy_score; i++) dp[i] = 0;
for (int i = 0; i < n; i++) {
for (int j = n_happy_score; j >= 0; j--) {
if(j >= score[i])
dp[j] = max(dp[j], dp[j - score[i]] + time[i]);
}
}
return sum_time - dp[n_happy_score];
}