关键活动
关键活动
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤ 100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1~N编号,M是子任务的数量,依次编号为1~M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。
输出格式:
如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。
输入样例:
7 8 1 2 4 1 3 3 2 4 5 3 4 3 4 5 1 4 6 6 5 7 5 6 7 2
输出样例:
17 1->2 2->4 4->6 6->7
解题思路
这道题在How Long Does It Take的基础上,求出所有的关键路径,并且按照要求打印出这些关键路径。
对于关键路径的输出格式,当时看了半天也没有看懂。其实并非把整一条关键路径给打印出来,而是把这条关键路径拆分为若干条边,再按照任务开始的点的编号按非递减的顺序输出,其中如果任务开始的点的编号相同,则按照任务结束的点的编号按非递增的顺序输出。
如果有多条关键路径(也就是说这几条关键路径是从源点到终点长度相同且最长的路径),也是一样的,把所有的关键路径都拆分为一条条的边,然后去重,打印的方式和上述一样。
举个例子,比如有
7 8
3 1 1
3 2 2
1 4 4
2 4 3
4 6 2
4 7 2
6 5 2
7 5 2
这个图有4条关键路径:
3->1->4->6->5
3->1->4->7->5
3->2->4->7->5
3->2->4->6->5
如图,以及存放这条路径的前一个顶点的矩阵:
把路径都拆成边,并去重。为了方便这里用矩阵来表示,aij == 1表示这条边存在,aij == 0表示这条边不存在。我们得到的矩阵如下:\begin{pmatrix} 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 \end{pmatrix}
然后我们从上到下来读行,从右到左来读列,所以输出的顺序为:
1->4
2->4
3->2
3->1
4->7
4->6
6->5
7->5
所以在代码中,也使用这种方法。先用一个数组来保存每个节点的前一个节点,然后对关键路径的终点进行DFS,把每一条路径的边记录到这个二维数组中,最后再用上述方法打印所有的边。
AC代码如下:
1 #include <cstdio> 2 #include <vector> 3 #include <queue> 4 #include <algorithm> 5 using namespace std; 6 7 const int MAXN = 101; 8 bool path[MAXN][MAXN] = {false};// 标识关键路径的边是否存在的二维数组 9 std::vector<int> p[MAXN]; // p[i]表示过点i的前一个顶点,因为会有多条关键路径,所以前一个顶点可能会有多个,所以用数组来记录 10 11 struct Edge { 12 int to, wt; 13 }; 14 15 struct Graph { 16 std::vector<std::vector<Edge> > G; 17 int verN, edgeN; 18 }; 19 20 Graph *createGraph(int n, int m); 21 void solve(Graph *graph); 22 void DFS(int src); 23 24 int main() { 25 int n, m; 26 scanf("%d %d", &n, &m); 27 Graph *graph = createGraph(n, m); 28 solve(graph); 29 30 return 0; 31 } 32 33 Graph *createGraph(int n, int m) { 34 Graph *graph = new Graph; 35 graph->verN = n; 36 graph->edgeN = m; 37 graph->G.resize(graph->verN + 1); 38 39 for (int i = 1; i <= graph->edgeN; i++) { 40 int v, w, wt; 41 scanf("%d %d %d", &v, &w, &wt); 42 graph->G[v].push_back({w, wt}); 43 } 44 45 return graph; 46 } 47 48 void solve(Graph *graph) { 49 int indeg[graph->verN + 1] = {0}, dist[graph->verN + 1] = {0}, cnt = 0; 50 for (int v = 1; v <= graph->verN; v++) { 51 for (auto &it : graph->G[v]) { 52 indeg[it.to]++; 53 } 54 } 55 56 std::queue<int> q; 57 for (int v = 1; v <= graph->verN; v++) { 58 if (indeg[v] == 0) q.push(v); 59 } 60 while (!q.empty()) { 61 int v = q.front(); 62 q.pop(); 63 cnt++; 64 65 for (auto &it : graph->G[v]) { 66 if (dist[v] + it.wt > dist[it.to]) { // 如果到该点距离比之前记录的更长 67 dist[it.to] = dist[v] + it.wt; // 更新为最长距离 68 p[it.to].clear(); // 同时把之前记录的所有前一个顶点全部清除 69 p[it.to].push_back(v); // 记录新的最长路径的前一个顶点,也就是弹出的这个点 70 } 71 else if (dist[v] + it.wt == dist[it.to]) { // 如果到该点距离相同 72 p[it.to].push_back(v); // 记录这个弹出的点 73 } 74 if (--indeg[it.to] == 0) q.push(it.to); 75 } 76 } 77 78 if (cnt != graph->verN) { 79 printf("0"); 80 return; 81 } 82 83 int max = dist[1]; 84 for (int v = 2; v <= graph->verN; v++) { 85 if (dist[v] > max) max = dist[v]; // 找到最长路径的长度 86 } 87 printf("%d\n", max); 88 89 for (int v = 1; v <= graph->verN; v++) { // 遍历一遍dist数组,只要dist[i] == max,说明i是关键路径的终点,进行DFS把这条路径的所有边标识到二维矩阵中 90 if (dist[v] == max) DFS(v); 91 } 92 93 for (int v = 1; v <= graph->verN; v++) { // 行为从上到下 94 for (int w = graph->verN; w >= 1; w--) {// 列为从右到左 95 if (path[v][w]) printf("%d->%d\n", v, w); 96 } 97 } 98 } 99 100 void DFS(int src) { 101 for (auto &it : p[src]) { 102 path[it][src] = true; // 把src存放的所有前一个点都标识为true 103 DFS(it); // 同时对这个点进行DFS,把这条路径剩下的边用同样的方法标识出来 104 } 105 }
参考资料
PAT测试题目“关键活动”:https://segmentfault.com/a/1190000020389295
7-11 关键活动(一):https://blog.csdn.net/rxq20081235/article/details/60766993
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/14820654.html