小明每天都在开源社区上做项目,假设每天他都有很多项目可以选,其中每个项目都有一个开始时间和截止时间,假设做完每个项目后,拿到报酬都是不同的。由于小明马上就要硕士毕业了,面临着买房、买车、给女友买各种包包的鸭梨,但是他的钱包却空空如也,他需要足够的money来充实钱包。万能的网友麻烦你来帮帮小明,如何在最短时间内安排自己手中的项目才能保证赚钱最多(注意:做项目的时候,项目不能并行,即两个项目之间不能有时间重叠,但是一个项目刚结束,就可以立即做另一个项目,即项目起止时间点可以重叠)。
思路
1. 一道任务调度题, 为此专门翻了下算法导论, 回顾了上面那道贪心可解的调度题目. 那道题目给定任务的起始点, 求解最大的不想交子集. 动态规划的状态转移方程为
S(i,j) = max(S(i,k), S(k+1, j) ) 其中 S(i,j) 表示任务 i, j 之间的时间段. 贪心解法是按照任务的结束时间排序, 优先选择完成时间早的
2. 编程之美上也有两道任务调度题. (1) n 个学生, m 个研究组, 每个学生可以参加自己喜欢的研究组, 如何安排见面会才能使得总时间最短. 解法是转化为图着色问题, 若一个学生同时对多个研究组感兴趣, 那么这些研究组之间各有直线连通, 用最少的色将图着色, 邻接的点颜色不同 (2) 有 N 场见面会要进行, 每一场见面会的起终时间给定, 求解至少需要多少场地才能让这些见面会能够正常进行. 解法是转化为区间图, 使用贪心算法求解, 尽量使用可以使用的, 序号最小的颜色着色
3. 这道题我本想着按照 (1) 的动规思路求解, 大致写了下状态转移方程, 觉得实现上可能会有难点, 尤其是判断某个区间内有哪些任务的问题, 觉得会很复杂. 取搜了下资料, 看到了两个关键字, 背包. 于是用背包的思路想了下, 写了状态转移方程.
dp[i] 表示第 i 项任务截止时间前能够获得的最大收益. dp[i] = max(dp[j]) + value[i]. j < i
o(n*n) 的时间复杂度, AC 了
4. 我记得以前做 kedebug 上的题目时也遇到过这种题目, 有时间要把任务调度的题目总结下. 记得去年 works application 给了一道笔试题就是任务调度的
代码
#include <iostream> #include <stdio.h> #include <vector> #include <memory.h> #include <algorithm> using namespace std; class Task { public: int st, ed, value; Task(int _st, int _ed, int _value):st(_st), ed(_ed), value(_value) { //cout << st << " " << ed << " " << value << endl; } Task() { Task(0, 0, 0); } bool operator<(const Task &ths) const { return this->ed < ths.ed; } }; int n; int dp[10010]; vector<Task> tasks; int main() { while(scanf("%d", &n) != EOF) { int st, ed, value; tasks.clear(); for(int i = 0; i < n; i ++) { scanf("%d%d%d", &st, &ed, &value); tasks.push_back(Task(st, ed, value)); } sort(tasks.begin(), tasks.end()); memset(dp, 0, sizeof(int)*(n+2)); dp[0] = 0; for(int i = 1; i <= n; i ++) { int start = tasks[i-1].st; dp[i] += tasks[i-1].value; int maxval = 0; for(int j = 0; j+1 < i; j ++) { if(tasks[j].ed > start) continue; maxval = max(maxval, dp[j+1]); } dp[i] += maxval; } int res =0; for(int i = 1; i <= n; i ++) res = max(res, dp[i]); printf("%d\n", res); } return 0; }