一、知识储备

背包问题,分为01背包,完全背包和多重背包。

相应的分析,代码构成,解题思路,请参考《王道论坛计算机考研机试指南》。(可能总结,先占个坑)

二、参考top1002

多读两遍题目,必然能领会出这是个背包问题。

由于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