浙江大学PTA数据结构预算法题目集的7-11,关键活动的题目,最后一个测试点总是通不过
题目链接以及描述:7-11 关键活动:
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和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
测试点信息:
代码如下
#include <stdio.h>
#include <stdlib.h>
/*===================*/
/*图基本操作*/
#define MAX_SIZE 100
typedef enum{false,true} bool;
typedef struct enode ENode, *Edge; // 边结点、链表声明
typedef struct vnode VNode;
struct enode {
int adjVex; // 该弧邻接到的结点
int weight;
ENode* next; // 下一条边的指针
};
struct vnode {
int data;
Edge first; // 该顶点的首条边,也是与该顶点邻接的边链表的头指针
};
typedef struct list_graph {
int EdgeNum,VexNum;
VNode Vex[MAX_SIZE]; // 顶点集
} *LGraph;
LGraph newLGraph(int Vnum, int Enum) {
LGraph LG = malloc(sizeof(struct list_graph));
for(int v=0;v<MAX_SIZE;v++) {
LG->Vex[v].data = v;
LG->Vex[v].first = NULL;
}
LG->VexNum = Vnum;
LG->EdgeNum = Enum;
return LG;
}
void addEdge_LG(LGraph G, int start, int end, int cost) {
// 处理弧头结点,在其邻接的边链表后头插一个新边
// 处理弧头结点,在其邻接的边链表后头插一个新边
Edge e = malloc(sizeof(struct enode));
e->adjVex = end;
e->weight = cost;
e->next = G->Vex[start].first;
G->Vex[start].first = e;
}
/*===================*/
/*拓扑排序、求关键路径*/
int Topo[MAX_SIZE];
bool TopoSort(LGraph G) {
int Vnum = G->VexNum;
int stack[Vnum],inDegree[Vnum];
int top = -1,count = 0;
for(int i=0;i<Vnum;i++)
inDegree[i] = 0; // 初始化入度数组
for(int v=0;v<Vnum;v++) { // 构建入度数组
for(Edge e=G->Vex[v].first;e!=NULL;e=e->next)
inDegree[e->adjVex]++;
}
// 所有入度为0的顶点入栈
for(int v=0;v<Vnum;v++) {
if(inDegree[v] == 0) stack[++top] = v;
}
// 栈非空,则仍有出度为0的顶点
while(top>=0) {
int stackTop = stack[top--]; // 弹出栈顶
Topo[count++] = stackTop; // 弹出元素放入逆拓扑序列
// 将栈顶顶点的邻接点入度-1,若导致出度为0则入栈
for(Edge e=G->Vex[stackTop].first;e!=NULL;e=e->next)
if(!--inDegree[e->adjVex])
stack[++top] = e->adjVex;
}
return count < Vnum ? false : true;
}
void keyPath(LGraph G) {
int Vnum = G->VexNum;
bool flag = TopoSort(G);
if(!flag) { // 存在环路,直接返回
printf("0");
return;
}
int ve[Vnum],vl[Vnum];
for(int i=0;i<Vnum;i++)
ve[i] = 0; // 初始化ve
for(int i=0;i<Vnum;i++) {
int v = Topo[i]; // 取拓扑排序顶点序号
// 更新顶点v邻接点的最早发生时间ve
for(Edge e=G->Vex[v].first;e!=NULL;e=e->next) {
// vi最早发生时间取决于邻接到vi的较长的路径
ve[e->adjVex] = ve[v] + e->weight > ve[e->adjVex] ? ve[v] + e->weight : ve[e->adjVex];
}
}
for(int i=0;i<Vnum;i++)
// 初始化最晚发生时间vl,汇点vl与ve相同,故方便起见将其初始化为汇点ve值
vl[i] = ve[Topo[Vnum-1]];
for(int i=Vnum-1;i>=0;i--) {
int v = Topo[i]; // 取逆拓扑排序顶点序号
//根据顶点v的邻接点更新v的最晚发生时间
for(Edge e=G->Vex[v].first;e!=NULL;e=e->next) {
// 最晚发生时间取决于所有(邻接点最晚发生时间-边权)的最小值
vl[v] = vl[e->adjVex] - e->weight > vl[v] ? vl[v] : vl[e->adjVex] - e->weight;
}
}
printf("%d\n",ve[Topo[Vnum-1]]);
// 判断关键路径
for(int v=0;v<Vnum;v++) {
for(Edge p=G->Vex[v].first;p!=NULL;p=p->next) {
// 某活动i->j最早开始时间=事件i的最早发生时间
// 某活动i->j最晚开始时间=事件j的最晚发生时间 - 活动耗时
int e = ve[v], l = vl[p->adjVex] - p->weight;
if(e==l) {
printf("%d->%d\n",v+1,p->adjVex+1);
}
}
}
}
/*===================*/
/*主函数*/
int main() {
int V=0,E=0;
scanf("%d%d",&V,&E);
LGraph G = newLGraph(V,E);
for(int i=0;i<E;i++) {
int start,end,weight;
scanf("%d%d%d",&start,&end,&weight);
addEdge_LG(G,start-1,end-1,weight);
}
keyPath(G);
return 0;
}