拓扑排序 最短工期 PTA

题目:

一个项目由若干个任务组成,任务之间有先后依赖顺序。项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务。现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工时间。

输入格式:

首先第一行给出两个正整数:项目里程碑的数量 N)和任务总数 M。这里的里程碑从 0 到 N1 编号。随后 M行,每行给出一项任务的描述,格式为“任务起始里程碑 任务结束里程碑 工作时长”,三个数字均为非负整数,以空格分隔。

输出格式:

如果整个项目的安排是合理可行的,在一行中输出最早完工时间;否则输出”Impossible”。

输入样例 1:

9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4

输出样例 1:

18

输入样例 2:

4 5
0 1 1
0 2 2
2 1 3
1 3 4
3 2 5

输出样例 2:

Impossible

判断一个图是否为有向无环图的算法:

1、定义一个队列Q,并把所有入度为0的结点加入队列。

2、取队首结点,输出,然后删除所有从它出发的边,并令这些边到达的定点的入度减1,如果某个顶点的入度减为0,则将其加入队列。

3、反复进行2的操作,直到队列为空。如果队列为空时入过队的结点数恰好为N,说明拓扑排序成功,图G为有向无环图;否则,拓扑排序失败,图G中有环。

最早发生时间ve的求法

求ve[j]的时候需要找到前继结点,比较麻烦,而通过前驱结点去寻找后继结点比较容易,那么在拓扑排序时访问到某个结点v时,更新它所有的后继结点的ve最大值,ve[u]+g[u][v]>ve[v](更新u的所有后继结点v)。 有可能出现多个起点或者终点,这时候找出最大的ve即为整个工程的最早发生时间。

最晚发生时间的求法

按求最早发生时间时入栈顺序出栈,出栈的每个元素更新它后继结点的最晚发生时间,此时,它后继结点一定已经出栈

关键路径的求法

最早发生时间与最晚发生时间相等的点即为关键路径

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<cstdio>
#include<ctime>
#include<list>
#include<sstream>
#include<iomanip>
#include<functional>
//#include<bits/stdc++.h>
#define LL long long
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w",stdout)
#define CLOSEIN fclose(stdin)
#define CLOSEOUT fclose(stdout)
using namespace std;
const int MAXV = 105;
vector<pair<int, int> > g[MAXV];//图,first为后继点,second为边长
int n, m;
int indegree[MAXV] = { 0 };//每个点的入度
int ve[MAXV] = { 0 };//最早发生时间
int vl[MAXV];
stack<int> toporder;//入度为0的点出队时依次进栈
int criticalpath() {//计算最晚发生时间和输出关键活动,返回关键路径长度,适用于汇点确定且唯一的情况
	fill(vl, vl + n, ve[n - 1]);//vl数组初始化为汇点的ve值
	//直接使用toporder出栈即为逆拓扑序列,求解vl数组
	while (!toporder.empty()) {
		int u = toporder.top();
		toporder.pop();
		for (int i = 0; i < g[u].size(); i++) {
			int v = g[u][i].first;//u的后继结点
			//用u的所有后继结点v的vl值来更新vl[u]
			if (vl[v] - g[u][i].second < vl[u]) {
				vl[u] = vl[v] - g[u][i].second;
			}
		}
	}
	//上面计算最晚发生时间,下面输出关键活动
	//遍历邻接表的所有边,计算活动的最早发生时间与最迟开始时间
	for (int u = 0; u < n; u++) {
		for (int i = 0; i < g[u].size(); i++) {
			int v = g[u][i].first, w = g[u][i].second;
			//e活动最早开始时间,l活动最晚发生时间
			int e = ve[u], l = vl[v] - w;
			//e==l说明,活动u->v是关键活动
			if (e == l)cout << u << "->" << v << endl;
		}
	}
	return ve[n - 1];//返回关键路径长度
}
bool toplogicalsort() {
	queue<int> q;
	for (int i = 0; i < n; i++) {//入度为0的点进队
		if (indegree[i] == 0)q.push(i);
	}
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		toporder.push(u);
		for (int i = 0; i < g[u].size(); i++) {
			int v = g[u][i].first;//记录此后继结点
			indegree[v]--;//后继点入度减1
			if (indegree[v] == 0)q.push(v);//后继点入度为0时入队

			if (ve[u] + g[u][i].second > ve[v])//最早发生时间取最大值
				ve[v] = ve[u] + g[u][i].second;
		}
	}
	if (toporder.size() == n)return true;//如果有n个结点,说明是有向无环图
	else return false;
}
int main() {
	cin >> n >> m;
	int a, b, c;
	while (m--) {
		cin >> a >> b >> c;
		g[a].push_back(make_pair(b, c));
		indegree[b]++;
	}
	if (toplogicalsort()) {//有多个终点时
		int k = 0;
		for (int i = 0; i < n; i++)
			k = max(k, ve[i]);
		cout << k << endl;
	}
	else cout << "Impossible" << endl;
	//criticalpath();计算最晚发生时间,输出关键路径,返回关键路径长度
}

    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/y201619819/article/details/79631816
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞