好好写一下这题的结题报告。这题带给我的启发挺多的。
先看一下单机调度问题:
假设有一台机器,以及在此机器上处理的n个作业a1,a2,...,an的集合。处理作业aj所需的时间为tj,作业aj的完成带来的收益为pj,作业aj完成的最后期限为dj。机器在一个时刻只能处理一个作业,而且如果某作业被处理,那么一定要在连续的时间内进行处理。如果某作业aj在最后期限dj之前完成,则获得收益pj,若在最后期限之前没有完成,则没有收益。请给出一个算法,来寻找能获得最大收益的调度,假设所有作业所需的处理时间都为整数。
单机调度问题,跟背包问题看上去多少有点像,类比的思想让我们联想到用两个维度来描述状态:一个是时间的长度,一个是作业的数量。
于是,我们可以设s[n,T]为 将前n个作业在时间T内进行调度,能够获得的最大利益。
然后,我们需要想出这个状态能够转移到哪里或者能够从哪些状态转移过来。
可是。。。问题出现了,我们发现,状态根本转移不鸟!原因在于,我们发现,前n个作业的顺序 matters!再看看背包问题,我们之所以可以用2个维度就搞定,也是因为在背包问题里面,顺序是不要紧的。
都是那该死的顺序,导致我们没办法递推下去。为了消除顺序带给我们的不定性,我们需要一点技巧来让顺序固定下来。
直觉上,那些deadline越早的作业应该越放在前面,这是很明显的。假设存在一个最优调度: b1,b2,b3....bk,假设里面存在相邻的两项b[i]和b[i+1],并且d[i] > d[i+1],也就是说,b[i]的deadline比较晚,却放在了前面。
如果我们把b[i]和b[i+1]交换,看看结果会发生什么变化?
首先,由于我们只是把b[i]和b[i+1]交换,所以由b1, b2,...,b[i-1] 和 b[i+1],b[i+2],...,b[k]产生的那部分利益没有发生变化。然后看看b[i],现在b[i]的完成时间推迟了,但是没事,因为之前b[i+1]的deadline比较早,但是b[i+1]都没有事,现在b[i]的deadline比较晚,更加没事啦!同样的分析,我们可以知道b[i+1]也是没事的。
也就是说,对于某个作业序列,我们把它按照di从小到大排序,排序后的序列跟原序列相比,利益是不会变差的! (*)
有了(*),我们可以放心地将问题的条件加强,我们规定,处理作业的顺序必须按照di从小到大来处理,有了这个规定,问题的最终答案也不会受到影响,而且,顺序被定下来了,对于整个问题的答案,受影响的因素缩小:对于每个作业做一个决策,到底是处理还是不处理(变成了01背包问题)。
于是,我们先把a1,a2...an按照di排序,排序完以后,我们前面设计的s[n,t]这下子就可以转移了。
对于s[n,T]来说,有两种选择:
1. 我们决定处理a[n],所以我们必须先把前n-1个作业在时间T - t[n]内调度好,然后处理作业a[n]。为什么不用考虑作业a[n]有可能被插入到前面的n-1个作业中间执行呢?原因是 (*)
2. 我们决定不处理a[n],这时候,我们只需要把前n-1个作业在时间T内搞定就行了。
也就是说,转移方程为
s[n, T] = s[n-1, T - t[n]] + p[n] 或者 s[n-1, T]
由于消除了顺序,所以最后的方程变成了跟背包问题的形式。
问题完满解决了。
这个问题带给我们的经验是,
调度问题中,顺序如果是有关的话,必须想个办法把顺序消除。可以先假设一个最优解,然后用“交换相邻项”或者“剪切粘贴”技术来寻找突破口。
然后看回这题。这道题里面,也是关于调度的,顺序也是有关的。所以我们看看能不能用某种方法把顺序定下来。
直觉上,那些分发时间越长的作业,应该越早地处理。于是,我们重施故技。假设存在一个花费时间最少的作业序列a1,a2,...an,里面存在相邻的两项a[i]和a[i+1],并且L[i] < L[i+1],也就是a[i]处理时间短却被放在了前面。假设我们交换这两项,画个图(语言表述会有点啰嗦= =),我们会发现,交换后得到的序列的总的时间花费是不会变差的!
也就说,我们可以规定作业序列完成的顺序是按照L[i]字段排序的。
ok,这道题就解决了,因为这道题不涉及某个作业不被处理的情况,在这道题里面,每个作业都必须被处理。所以我们只需要做个排序就搞定这题了。正因为如此,这道题的做法,被归类为“贪心”,而不是“动态规划”。在我看来,贪心其实就是特殊情况下的动态规划。
#include <algorithm>
#include <vector>
#include <cstdio>
using namespace std;
struct Task {
int T;
int L;
};
struct __cmptask {
bool operator() (const Task & a, const Task & b) const {
return a.L > b.L;
}
} cmpTask;
vector<Task> tasks;
int N;
void run() {
int i;
sort(tasks.begin(), tasks.end(), cmpTask);
int curLatest = -999;
int timeCost = 0;
for (i = 0; i < N; ++i) {
timeCost = timeCost + tasks[i].T;
curLatest = max(curLatest, timeCost + tasks[i].L);
}
printf("%d\n", curLatest);
}
int main() {
scanf("%d", &N);
tasks.resize(N);
int i;
for (i = 0; i < N; ++i) {
scanf("%d", &(tasks[i].T));
}
for (i = 0; i < N; ++i) {
scanf("%d", &(tasks[i].L));
}
run();
return 0;
}