AOE网上的关键路径(拓扑排序+SPFA算法)

AOE网上的关键路径

Time Limit: 1000ms   Memory limit: 65536K  

题目描述

    一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图。 
   
 AOE(Activity On Edge)网:顾名思义,用边表示活动的网,当然它也是DAG。与AOV不同,活动都表示在了边上,如下图所示:
                                     

    
如上所示,共有11项活动(11条边),9个事件(9个顶点)。整个工程只有一个开始点和一个完成点。即只有一个入度为零的点(源点)和只有一个出度为零的点(汇点)。
    
关键路径:是从开始点到完成点的最长路径的长度。路径的长度是边上活动耗费的时间。如上图所示,到 579是关键路径(关键路径不止一条,请输出字典序最小的),权值的和为18

输入

    这里有多组数据,保证不超过10组,保证只有一个源点和汇点。输入一个顶点数n(2<=n<=10000),边数m(1<=m <=50000),接下来m行,输入起点sv,终点ev,权值w1<=sv,ev<=n,sv != ev,1<=w <=20)。数据保证图连通。

输出

    关键路径的权值和,并且从源点输出关键路径上的路径(如果有多条,请输出字典序最小的)。

示例输入

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

示例输出

18
1 2
2 5
5 7
7 9

提示

注意:本题题意是1为起点,n 为终点,故没有查找源点和汇点。

下面的阐述中,设AOE网的起点为v0终点为vn.

1.关键路径

AOE网中,从事件i到j的路径中,加权长度最大者称为i到j的关键路径(Critical Path),记为cp(i,j)。特别地,始点0到终点n的关键路径cp(0,n)是整个AOE的关键路径。

显然,关键路径决定着AOE网的工期,关键路径的长度就是AOE网代表的工程所需的最小工期。

2.事件最早/晚发生时间

事件vi的最早发生时间ve(i)定义为:从始点到vi的最长(加权)路径长度,即cp(0,i)

事件vi的最晚发生时间vl(i)定义为:在不拖延整个工期的条件下,vi的可能的最晚发生时间。即vl(i) = ve(n) - cp(i, n)

3.活动最早/晚开始时间

活动ak=<vi, vj>的最早开始时间e(k):等于事件vi的最早发生时间,即

     e(k) = ve(i) = cp(0, i)

活动ak=<vi, vj>的最晚开始时间l(k)定义为:在不拖延整个工期的条件下,该活动的允许的最迟开始时间,即

        l(k) = vl(j) – len(i, j)

这里,vl(j)是事件j的允许的最晚发生时间,len(i, j)是ak的权。

活动ak的最大可利用时间:定义为l(k)-e(k)

 4.关键活动

若活动ak的最大可利用时间等于0(即(l(k)=e(k)),则称ak 为关键活动,否则为非关键活动。

显然,关键活动的延期,会使整个工程延期。但非关键活动不然,只要它的延期量不超过它的最大可利用时间,就不会影响整个工期。

关键路径的概念,也可以用这里的关键活动定义,即有下面的:

(一) 基本算法

    关键路径算法是一种典型的动态规划法,这点在学了后面的算法设计方法后就会看到。下面就来介绍该算法。设图G=(V, E)是个AOE网,结点编号为1,2,...,n,其中结点1与n 分别为始点和终点,ak=<i, j>∈E是G的一个活动。

 

根据前面给出的定义,可推出活动的最早及最晚发生时间的计算方法:

  e(k) = ve(i)

  l(k) = ve(j) - len(i,j)  

结点的最早发生时间的计算,需按拓扑次序递推:

                 ve(1) = 0

                 ve(j) = MAX{ ve(i)+len(i, j) } 

对所有<i,j> ∈E的i  结点的最晚发生时间的计算,需按逆拓扑次序递推:

                 vl(n) = ve(n)

                 vl(i) = MIN{vl(j) - len(i, j)} 对所有<i,j>∈E的j 

关于 ve与vl的求法,可参阅图 21‑5。

这种计算方法, 依赖于拓扑排序, 即计算ve( j) 前,应已求得j 的各前趋结点的ve值,而计算vl(i)前,应已求得i的各后继结点的vl值。ve的计算可在拓扑排序过程中进行,即在每输出一个结点i后,在删除i的每个出边<i,j>(即入度减1)的同时,执行

                if ( ve[i]+len(i,j)) > ve[j] ) 

                ve[j] = ve[i] + len(i,j) 

实际上,该操作对i的每个后继j分别进行一次。因此对程序作少量扩充即可求得ve。

vl的值可按类似的方法在逆拓扑排序过程(即直接按与拓扑序列相反的次序输出结点的过程)中求得,但一般不必专门这样进行。事实上,通过逆方向使用拓扑序列即可递推出各vl的值,假定拓扑序列是topoSeq,则vl 的值的求法为(结点编号为1~n)。

       

#include<iostream>
#include<stack>
#include<cstring>
#define MaxN 10010
#define MaxM 50010
using namespace std;
struct node
{
    int d,to,on;
    node *next;
}*ls1[MaxN],*ls2[MaxN];
int cu1[MaxN],cu2[MaxN];
int Ee[MaxN],El[MaxN];
int e[MaxM],l[MaxM];
int n,m;
void O_TopSort()//正序拓扑
{
    memset(Ee,0,sizeof(Ee));
    stack<int >s;
    for(int i=1; i<=n; i++)
        if(!cu1[i])
            s.push(i);
    int t,k;
    node *p;
    while(!s.empty())
    {
        t=s.top();
        s.pop();
        p=ls1[t];
        while(p)
        {
            k=p->to;
            cu1[k]--;
            if(cu1[k]==0)
                s.push(k);

            if(Ee[t]+p->d>Ee[k])
                Ee[k]=Ee[t]+p->d;
            p=p->next;
        }
        // ls1[t]=NULL;
    }
//    for(int i=1; i<=n; i++)
//        cout<<Ee[i]<<" ";
}
void R_TopSort()//逆序拓扑
{
    memset(El,0,sizeof(El));
    stack<int >s;
    for(int i=1; i<=n; i++)
    {
        El[i]=Ee[n];
        if(!cu2[i])
            s.push(i);
    }
    int t,k;
    node *p;
    while(!s.empty())
    {
        t=s.top();
        s.pop();
        p=ls2[t];
        while(p)
        {
            k=p->to;
            if(--cu2[k]==0)
                s.push(k);
            if(El[t]-p->d<El[k])
                El[k]=El[t]-p->d;
            p=p->next;
        }
        // ls2[t]=NULL;
    }
//    for(int i=1;i<=n;i++)
//        cout<<El[i]<<" ";
}
int main()
{
    while(cin>>n>>m)
    {
        memset(ls1,0,sizeof(ls1));
        memset(ls2,0,sizeof(ls2));
        memset(cu1,0,sizeof(cu1));
        memset(cu2,0,sizeof(cu2));
        int a,b,c;
        for(int i=1; i<=n; i++)
        {
            ls1[i]=NULL;
            ls2[i]=NULL;
        }
        for(int i=0; i<m; i++)
        {
            cin>>a>>b>>c;
            node *p=new node;
            p->d=c;
            p->to=b;
            p->on=i;
            cu1[b]++;
            p->next=ls1[a];
            ls1[a]=p;
            node *q=new node ;
            q->d=c;
            q->to=a;
            p->on=i;
            cu2[a]++;
            q->next=ls2[b];
            ls2[b]=q;
        }
        O_TopSort();
        cout<<Ee[n]<<endl;
        R_TopSort();
        memset(e,0,sizeof(e));
        memset(l,0,sizeof(l));
        node *p;
        int k,j;
        for(int i=1; i<=n; i++)
        {
            p=ls1[i];
            int Min;
            int flag=0;
            if(p)
                Min=p->to;
            while(p)
            {

                j=p->to;
                k=p->on;
                e[k]=Ee[i];
                l[k]=El[j]-p->d;
                if(e[k]==l[k])
                {
                    if(Min>j)      //如果关键路径有多条,比较
                    {
                        Min=j;
                    }
                    flag=1;
                }
                p=p->next;
            }
            if(flag)
            {
                cout<<i<<" "<<Min<<endl;
                i=Min-1;            //直接跳转到当前关键路径节点的下一节点
                ls1[i]=NULL;        //将字典序最小的赋值
            }
        }
    }
    return 0;
}


还可以用SPFA求最长路径:

如果正向建图的话,比如下图:如果现在终点是6,现在2和3都能使6的距离达到最大且值相同。我们处理的时候会选2,但还是2这条路径却不是最优的,反而3是最优的。
所以我们逆向见图,求一个最短路然后倒着输出就好了。

 
 

   解决方式:

     倒序建图,当松弛时(u,v),遇到相同的情况,尽量使u变的更小,那么最终得到就是最小的字典序。

     对于求最长路径,将dis设为-INF,dis[s] = 0 

#include<iostream>
#include<queue>
#include<cstring>
#include<limits.h>
#define Max INT_MAX
#define MaxM 50010
#define MaxN 10010
using namespace std;
struct node
{
    int to;
    int we;
    node *next;
} *lis[MaxN];
int path[MaxN];//值为上一节点,下标为下一节点
int dist[MaxN];
int n,m;
void SPFA_BFS(int v0)
{
    int v[MaxN];
    for(int i=0; i<n; i++)
    {
        dist[i]=-Max;
        path[i]=-1;
        v[i]=0;
    }
    dist[v0]=0;
    v[v0]++;
    queue<int >q;
    while(!q.empty())
        q.pop();
    q.push(v0);
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        v[t]--;
        node *tmp=lis[t];
        while(tmp)
        {
            int k=tmp->to;
            if(dist[t]+tmp->we>dist[k]||dist[t]+tmp->we==dist[k]&&path[k]>t)//若路径相等,则比较后选择小的
            {
                dist[k]=dist[t]+tmp->we;
                path[k]=t;
                if(!v[k])
                {
                    q.push(k);
                    v[k]++;
                }
            }
            tmp=tmp->next;
        }
        lis[t]=NULL;
    }
}
int main()
{
    while(cin>>n>>m)
    {
        int a,b,c;
        memset(lis,0,sizeof(lis));
        for(int i=0; i<m; i++)
        {
            cin>>a>>b>>c;
            a--;
            b--;

            node *q=new node; //倒叙建图
            q->we=c;
            q->to=a;
            q->next=lis[b];
            lis[b]=q;
        }
        SPFA_BFS(n-1);
        cout<<dist[0]<<endl;
        int t=0;
        while(t!=n-1)
        {
            cout<<t+1<< " "<<path[t]+1<<endl;
            t=path[t];
        }

    }
    return 0;
}


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值