题目描述
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1N编号,M是子任务的数量,依次编号为1M。随后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
代码
#include<stdio.h>
#include<malloc.h>
#include<stdbool.h>
#define MaxVertexNum 100
int Day[MaxVertexNum] = {0};
int Latest[MaxVertexNum] = {0};
int Indegree[MaxVertexNum] = {0};
int TopOrder[MaxVertexNum] = {0};
int InQue[MaxVertexNum] = {0};
typedef int Vertex;
typedef int WeightType;
typedef struct ENode *Edge;
struct ENode{
Vertex V1;
Vertex V2;
WeightType Weight;
};
typedef struct AdjNode *PtrToAdj;
struct AdjNode{
Vertex V;
PtrToAdj Next;
WeightType Weight;
};
typedef struct HNode{
PtrToAdj FirstEdge;
}Head[MaxVertexNum];
typedef struct GNode *LGraph;
struct GNode{
int Nv;
int Ne;
Head G;
};
LGraph CreateGraph(int N)
{
LGraph Graph = (LGraph)malloc(sizeof(struct GNode));
Graph->Nv = N;
Graph->Ne = 0;
Vertex i;
for( i=0; i!=N; ++i ){
Graph->G[i].FirstEdge = NULL;
}
return Graph;
}
void InsertEdge(Edge E, LGraph Graph)
{
PtrToAdj A = (PtrToAdj)malloc(sizeof(struct AdjNode));
A->V = E->V2;
A->Next = Graph->G[E->V1].FirstEdge;
A->Weight = E->Weight;
Graph->G[E->V1].FirstEdge = A;
}
LGraph BuildGraph()
{
int Nv, i;
scanf("%d",&Nv);
LGraph Graph = CreateGraph(Nv);
scanf("%d",&Graph->Ne);
Edge E = (Edge)malloc(sizeof(struct ENode));
for( i=0; i!=Graph->Ne; ++i ){
scanf("\n%d %d %d",&E->V1,&E->V2,&E->Weight);
--E->V1;--E->V2;
InsertEdge(E, Graph);
++Indegree[E->V2];
}
return Graph;
}
Vertex Que[MaxVertexNum] = {0};
int first = 0, last = -1;
bool TopSort(LGraph Graph)
{
Vertex i;
int cnt = 0;
PtrToAdj W;
for( i=0; i!=Graph->Nv; ++i)
if( !Indegree[i] )
Que[++last] = i;
while(first<=last){
i = Que[first++];
TopOrder[cnt++] = i;
for( W=Graph->G[i].FirstEdge; W; W = W->Next ){
if(W->Weight+Day[i]>Day[W->V])
Day[W->V] = W->Weight + Day[i];
if(--Indegree[W->V]==0)
Que[++last] = W->V;
}
}
if(cnt!=Graph->Nv)
return false;
else
return true;
}
LGraph SwapGraph(LGraph Graph)
{
LGraph Graph_swap = CreateGraph(Graph->Nv);
Graph_swap->Ne = Graph->Ne;
Vertex V;
PtrToAdj W, P;
Edge E = (Edge)malloc(sizeof(struct ENode));
for( V=0; V!=Graph->Nv; ++V)
Indegree[V] = 0;
for( V=0; V!=Graph->Nv; ++V ){
for( W=Graph->G[V].FirstEdge; W; W=W->Next ){
E->V1 = W->V;
E->V2 = V;
E->Weight = W->Weight;
InsertEdge(E, Graph_swap);
++Indegree[E->V2];
}
}
return Graph_swap;
}
void KeyActtivity(LGraph Graph_swap, LGraph Graph)
{
Vertex i;
first = 0;
last = -1;
PtrToAdj W;
int max=0;
for(i=0; i!=Graph->Nv; ++i){
if(Day[i]>max)
max = Day[i];
}
for(i=0; i!=Graph->Nv; ++i){
Latest[i]=65535;
if(Indegree[i]==0){
Que[++last]=i;
InQue[i] = 1;
Latest[i] = max;
}
}
while(first<=last){
i = Que[first++];
for( W=Graph_swap->G[i].FirstEdge; W; W=W->Next ){
if(Latest[i]-W->Weight < Latest[W->V])
Latest[W->V] = Latest[i]-W->Weight;
if(--Indegree[W->V]==0)
Que[++last] = W->V;
}
}
for(i=0;i!=Graph->Nv;++i)
{
for(W=Graph->G[i].FirstEdge; W; W=W->Next)
if(Latest[W->V]-Day[i]==W->Weight)
printf("%d->%d\n",i+1,W->V+1);
}
}
int main()
{
LGraph Graph = BuildGraph();
if( !TopSort(Graph) ){
printf("0");
return 0;
}
else{
int max=0, i=0;
for(i=0;i!=Graph->Nv;++i)
if(Day[i]>max)
max = Day[i];
printf("%d\n",max);
LGraph Graph_swap = SwapGraph(Graph);
KeyActtivity(Graph_swap, Graph);
}
return 0;
}
解题思路
在上一道题How Long Does It Take的基础上增加一些内容。方法比较蠢比较容易理解。
建立一个新的图,起点和终点反过来,从终点往起点走,这样就可以计算得到每一个检查点最晚时间(图中红色数字)。判断是不是关键活动的方法是,比如检查点5到检查点7,检查点5最早需要在第7天(图中蓝色数字)到达,耗费4天(图中黑色数字)到达检查点7,一共11天,而检查点7最晚14天到达就可以,所以从检查点5到7不是关键活动,有时间富裕。
问题和解决方法
1.多起点多终点测试点不通过
多个终点的最小时间Day[]
不一样,比如一个最少需要10天,另一个需要12天,那么反过来计算最晚时间时,两个起点Latest[]
都应该初始化为12天。
而且往回算Latested[]
的时候,起点可能会是多个,所以不能用找Day[]
最大值的方法找,而应该找入度为0的点作为起点。
2.最大N测试点不通过
这个和最大N不最大N没什么关系,程序中修改Latest[]
的时候用了一个队列,先把起点入队,然后出队,对于这个出队节点的所有邻接点,如果没有入过队的话就入队。这个入队条件是错的。会出现这个节点x已经入队了,但是过一会儿有一个出队节点把x修改了,但是队中的x是没有修改过的,而修改过的正确的节点又不能入队,最后会出现错误。
入队条件应该是判断这个邻接点的入度是不是为0,如果为0,那么肯定不会有节点再修改它了,直接入队。
这个做题不能葫芦过去,想清楚再写。