拓扑排序——关键路径(AOE网络基本概念和模板题)

传送门:??没有

AOE网络:在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网。

以上来解释来自百度百科

图例:

每一个节点就是一个事件,每一条边就是一个活动。该节点的事件开始之前必须要等该节点的所有前驱事件都完成了才可以开始。

从起点到终点的路径有多条,其中长度最长的一条被称为关键路径,完成整个工程的最短时间就是最长路径的长度。关键路径可能有多条,所有关键路径上的节点事件都被称为关键事件。

求每个节点的最长路,可以在求出拓扑序之后按照拓扑序在拓扑图上求每一个节点的最长路,这个就是一个线性DP。下图就是一个动态规划方程。求完后到终点的距离就是关键路径的长度。

v1[k]=max(v1[k],v1[t]+w[j]); //其中t是k的前驱结点,v1[k]是从起点到k的最长距离

设len为关键路径的长度

关键活动:关键路径上的活动。

关键事件求法:一个节点是关键节点当且仅当该事件的最晚开始时间等于最早开始时间。

最早开始时间:该事件所有前驱事件中完成时间最晚的一个加上他们之间的活动的时间。

就是最长路,因为要等前驱事件都完成才能开始,所以选择了长度最长的前驱事件的时间。

最晚开始时间:用len减去该点到终点的最长路径的长度就是最晚开始时间。

求最早开始时间用最长路,求最晚开始时间需要建立一个逆序图用以下递推公式求

v2[k]=min(v2[k],v2[t]-w[j]); //其中在原图上k是t的后继节点

例题:

样例输入:

9 11
1 2 6
1 3 4
2 5 1
3 5 1
5 7 9
5 8 7
7 9 2
8 9 4
1 4 5
4 6 2
6 8 4

 样例输出:

1 2
2 5
5 8
5 7
7 9
8 9

思路:这里有一个小坑,关键活动是关键路径上的活动,是否等同于关键事件之间的活动?

           答案是否定的,如下图:其中1,3,4,6,都是关键事件,关键路径也是1-3-4-6,其中3和6之间的活动不是关键活动,意味着不能在得到关键事件之后,直接输出所有关键事件之间的所有活动。

判定一条边是关键活动,除了要保证两个端点都是关键事件之外,还要保证端点之间的边的距离是最长路。

可以用以下条件判定

if(st[k]&&(w[j]+v1[t]==v1[k]))

代码:

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef pair<int ,int>PII;
const int N=5010,M=5e5+10;
int e[M],ne[M],w[M],hh[N],tt[N],idx; //建一个正向和一个反向图
int d[N];  //记录入度
int q[N];  //队列
int v1[N],v2[N]; //v1记录最早开始时间,v2记录最晚开始时间
bool st[N]; //记录是否关键节点
vector<int>v;  //保存所有关键节点
void add(int h[],int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int n,m;
void topsort()
{
    int h1=0,t1=0;
    q[0]=1;
    while(h1<=t1)
    {
        int t=q[h1++];
        for(int i=hh[t];~i;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(d[j]==0) q[++t1]=j;
        }
    }
}
void get()
{
    for(int i=0;i<n;i++)
    {
        int t=q[i];
        for(int j=hh[t];~j;j=ne[j])
        {
            int k=e[j];
            v1[k]=max(v1[k],v1[t]+w[j]);
        }
    }
    memset(v2,0x3f,sizeof v2);  //
    v2[n]=v1[n];   //因为要从终点递推回去,所以一开始要得到终点的值
    for(int i=n-1;i>=0;i--)
    {
        int t=q[i];
        for(int j=tt[t];~j;j=ne[j])
        {
            int k=e[j];
            v2[k]=min(v2[k],v2[t]-w[j]);
        }
    }
}
void dfs(int u)
{
    if(u==n) return;
    for(int i=hh[u];~i;i=ne[i])
    {
        int j=e[i];
        if(v1[j]==v2[j]&&!st[j])
        {
            v.push_back(j);
            st[j]=true;
            dfs(j);
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(hh,-1,sizeof hh);
    memset(tt,-1,sizeof tt);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(hh,a,b,c);
        d[b]++;
        add(tt,b,a,c);
    }
    topsort();
     get();
    v.push_back(1);
    st[1]=true;
    dfs(1);
    for(int i=0;i<v.size();i++)
    {
        int t=v[i];
        for(int j=hh[t];~j;j=ne[j])
        {
            int k=e[j];
            if(st[k]&&(w[j]+v1[t]==v1[k]))
                printf("%d %d\n",t,k);
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值