题目:
一个项目由若干个任务组成,任务之间有先后依赖顺序。项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务。现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工时间。
输入格式:
首先第一行给出两个正整数:项目里程碑的数量 N(≤)和任务总数 M。这里的里程碑从 0 到 N−1 编号。随后 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
判断一个图是否为有向无环图的算法:
- 定义一个队列Q,并把所有入度为0的结点加入队列。
- 取队首结点,输出,然后删除所有从它出发的边,并令这些边到达的定点的入度减1,如果某个顶点的入度减为0,则将其加入队列。
- 反复进行2的操作,直到队列为空。如果队列为空时入过队的结点数恰好为N,说明拓扑排序成功,图G为有向无环图;否则,拓扑排序失败,图G中有环。
最早发生时间ve的求法
求ve[j]的时候需要找到前继结点,比较麻烦,而通过前驱结点去寻找后继结点比较容易,那么在拓扑排序时访问到某个结点v时,更新它所有的后继结点的ve最大值,ve[u]+g[u][v]>vev。 有可能出现多个起点或者终点,这时候找出最大的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();计算最晚发生时间,输出关键路径,返回关键路径长度
}