一、知识储备
背包问题,分为01背包,完全背包和多重背包。
相应的分析,代码构成,解题思路,请参考《王道论坛计算机考研机试指南》。(可能总结,先占个坑)
多读两遍题目,必然能领会出这是个背包问题。
由于project的不可分割,并且没有重复,所以这是个01背包问题。
下面将该题目与传统的01背包作对比。
首先,project和物品对应。
project的价值和物品价值对应。
project的时间和物品体积对应。
差别就出在这个地方,project有两个时间,该如何处理。
还有个要点就是,原始背包问题有个“最大的背包容量”,这个business里面似乎没有,不过能够确定肯定是某个“最大时间”的概念。
这里先给出几个结论:
1,如果每个project的deadline都无限大,那么这个business问题就是原始的背包问题(这个背包为无限大)。
2,该题对应01背包,即每个project只能选一个。
3,该题并不是恰好装满,这个差别反映在dp数组的初始化上。
4,由于该问题没有“背包最大容量”这个概念,所以使用maxProfit随时记录最大的收益。
不太理解的话,请看下节代码,会有体会。
程序中,使用maxDeadline变量记录最大的时间。
对于for(j 1:maxDeadLine)的循环,分三部分组成。
第一部分是j<nodes[i-1].lasting,也就是当前时间内无法完成第i个project,所以dp[i][j]=dp[i-1][j],也就是第i个项目肯定装不进背包。
第二部分是j>nodes[i-1].deadline,只记录当前nodes[i-1].value(这一步是难点,可以从下一层对上一层的依赖分析)
第三部分是 j 中间的那部分取值,也就是ndoes[i-1].lasting=<j<=nodes[i-1].deadline这部分,这里是使用dp[i][j]=max{dp[i-1][j],dp[i-1][j-nodes[i-1].lasting]+nodes[i-1].value}。
现在来分析差异,对于传统背包问题,for(j 1:maxDeadline)的j只背分成两部分,即第一部分和第三部分。由于传统背包里面没有deadline这个值(也可以把deadline理解为无穷大,这就是我第一个结论),所以不存在比deadline大的j。
细心的人一定注意到,nodes里面的元素被我sort过,排序原则是按照deadline由小到大排的,后处理的project的deadline一定要大于或等于前面的project的deadline,这也是为什么for(j 1:maxDeadline)循环的原因。传统01背包里面没有这一步,因为每个物品的deadline都认为是无穷大。
三、解题代码
#include<stdio.h>
#include<vector>
using std::vector;
#include<algorithm>
using std::sort;
using std::max;
struct node {
int value;
int lasting;
int deadline;
node(int value_ = -1, int lasting_ = -1, int deadline_ = -1) :value(value_), lasting(lasting_), deadline(deadline_) {}
bool operator<(const struct node &x)const {
return deadline < x.deadline;
}//operator<
};
int main() {
freopen("in.txt", "r", stdin);
int N(-1);
scanf("%d", &N);
vector<node> nodes(N);
int maxDeadline(0);
for (int i(0); i < N; ++i) {
scanf("%d%d%d", &nodes[i].value, &nodes[i].lasting, &nodes[i].deadline);
if (nodes[i].deadline > maxDeadline)maxDeadline = nodes[i].deadline;
}//for i
sort(nodes.begin(), nodes.end());
vector<vector<int>> dp(N + 1, vector<int>(maxDeadline + 1));
int maxProfit(0);
for (int i(1); i <= N; ++i) for (int j(1); j <= maxDeadline; ++j) {
if (j < nodes[i - 1].lasting) dp[i][j] = dp[i - 1][j];
else if (j > nodes[i - 1].deadline) dp[i][j] = nodes[i - 1].value;
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nodes[i - 1].lasting] + nodes[i - 1].value);
maxProfit = max(maxProfit,dp[i][j]);
}//for i,j
printf("%d\n", maxProfit);
return 0;
}//main
四、优化直觉
上面程序的dp数组定义为二维,N+1行容易理解。
其实每行拥有的元素个数不必都是maxDeadline+1,只是开成最大肯定没有错。
思考一下,第i行的元素个数可以简化为nodes[i].deadline+1个。
再多思考一下,if (dp[i][j] > maxProfit)maxProfit = dp[i][j];是收集成果的代码,对吧。
如果按照上面的分析,不难想到,成果只需要在for(j 1:maxDeadline)的第二部分收集。也就是把这行代码放到else{}里面,也是对的。
下面做个更刺激的,众所周知,传统01背包可以将dp数组压缩为一维,for(j)的遍历顺序必须从大往小。
相应的,该题的代码如下,将dp压缩为一维。
vector<int> dp(maxDeadline + 1, 0);
for (int i(1); i <= N; ++i) for (int j(maxDeadline); j >= nodes[i - 1].lasting; --j) {
if (j > nodes[i - 1].deadline)dp[j] = nodes[i - 1].value;
else dp[j] = max(dp[j],dp[j - nodes[i - 1].lasting] + nodes[i - 1].value);
}//for i,j
printf("%d\n", *max_element(dp.begin(), dp.end()));//求dp数组里面的最大元素,STL里面的max_element
更简洁。
董艳超,第一版,20170226