一、概述
给出一个图,首先判断是否为有向无环图,如果是,则输出要求的边的最早发生时间和最晚发生时间,然后输出所有关键路径。
判断是否为有向无环图,可以用拓扑排序来判断。
二、分析
首先分析拓扑排序。使用邻接矩阵储存图。
拓扑排序要用到的有:
入度数组,队列。
入度数组判断哪些元素入队。
如下:
int topo(int s)
{
if(indegree[s]!=0)
return 0;
int num=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int front=q.front();
//vis[front]=1;
num++;
q.pop();
for(int i=1;i<=N;i++)
{
if(G[front][i]!=0)
{
indegree[i]--;
if(indegree[i]==0)
{
q.push(i);
}
}
}
/*for(int i=1;i<=N;i++)
{
if(indegree[i]==0)
{
q.push(i);
}
}*/
}
if(num==N)
return 1;
else
return 0;
}
与层序遍历有些像,都是用队列,但是拓扑排序不需要vis数组,因为像这样写的拓扑排序,它根本就不会往回走,也就不需要判断是否走过了。一定注意一点,出队一个,然后遍历入度数组时,若减一出现了0,就要把它入队,这样才是正确的。不能先一个循环减一,再一个循环入队,那样就错了。
然后判断走过的节点个数,若是走完了,那就是无环,否则就是有环。
然后我们看关键路径需要的四大数组:
ve,vl,e,l:
ve:点的最早开始时间
vl:点的最迟开始时间
e:边的最早开始时间
l:边的最迟开始时间
在拓扑排序过程中,我们就可以确定ve的值了。
首先,源点的ve为0。源点相邻的下一个点的ve值,等于“所有指向这个点的ve的值加上他们之间边的边权中的最大值”。
我们观察一下拓扑排序的代码,在遍历某一点的所有边的时候,我们可以得到它指向的所有点,那么,就可以更新对应点的ve,如果和大于ve,则更新,否则不更新。
然后看vl。vl也有一个点可以确定,就是汇点,汇点的vl与汇点的ve是相等的。
剩余点的vl值,等于“它所指向的点的vl减去他们之间边权,这些值中的的最小值”。也就是说,我们需要从汇点,往回反拓扑排序。这时,可以使用栈,在拓扑排序时保存拓扑序列,然后出栈,就是我们要的反拓扑排序了。
这样所需要的拓扑排序函数如下:
int topo(int s)
{
if(indegree[s]!=0)
return 0;
int num=0;
queue<int> q;
q.push(s);
//这里保存反拓扑排序
adtopo.push(s);
ve[s]=0;
while(!q.empty())
{
int front=q.front();
//vis[front]=1;
num++;
adtopo.push(front);
q.pop();
for(int i=1;i<=N;i++)
{
if(G[front][i]!=0)
{
indegree[i]--;
//这里更新ve
if(ve[i]<ve[front]+G[front][i])
ve[i]=ve[front]+G[front][i];
if(indegree[i]==0)
{
q.push(i);
}
}
}
}
if(num==N)
return 1;
else
return 0;
}
然后更新vl。
我们的反拓扑排序序列存在栈中,则栈顶就是汇点。于是汇点的vl确定了。
将汇点弹出,进入循环。对于目前的栈顶,可以知道它所有的指向的值(如果是汇点后面的一个,那只有一个,就是汇点),根据这些指向的值的vl,可以更新该点的vl。
简而言之,ve是一个更新一大堆,vl是一大堆更新一个。如下:
if(flag==1)
{
int d=adtopo.top();
fill(vl,vl+1010,INF);
vl[d]=ve[d];
adtopo.pop();
while(!adtopo.empty())
{
int top=adtopo.top();
adtopo.pop();
for(int i=1;i<=N;i++)
{
if(G[top][i]!=0)
{
if(vl[i]-G[top][i]<vl[top])
vl[top]=vl[i]-G[top][i];
}
}
}
}
既然ve和vl更新完了,我们可以开始着手处理e和l了。
一条边的最早开始时间,就应该是它的起点的最早开始时间;
一条边的最晚开始时间,就应该是它的终点的最晚开始时间减去边权。
明确了这两点,e和l也就不难求了。
如下:
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
if(G[i][j]!=INF)
{
e[i][j]=ve[i];
l[i][j]=vl[j]-G[i][j];
if(e[i][j]==l[i][j])
cri[i][j]=1;
}
}
}
如果有多个源点或者多个汇点,那么设置一个大源点0,一个大汇点N+1,如下:
vector<int> r,d;
for(int i=1;i<=N;i++)
{
if(indegree[i]==0)
{
//r=i;
r.push_back(i);
//break;
}
}
for(int i=0;i<r.size();i++)
{
G[0][r[i]]=0;
indegree[r[i]]++;
}
int MAX=0;
for(int i=1;i<=N;i++)
{
if(ve[i]>MAX)
{
MAX=ve[i];
}
}
for(int i=1;i<=N;i++)
{
if(ve[i]==MAX)
d.push_back(i);
}
for(int i=0;i<d.size();i++)
{
G[d[i]][N+1]=0;
}
fill(vl,vl+1010,INF);
ve[N+1]=ve[d[0]];
vl[N+1]=ve[N+1];
对应修改即可。
输出关键路径,就是对cri矩阵进行DFS即可。如下:
void DFS(int s,int d)
{
if(s==d)
{
//ans.push_back(s);
int i;
for(i=0;i<ans.size()-1;i++)
{
printf("%d->",ans[i]);
}
printf("%d\n",ans[i]);
//ans.pop_back();
return;
}
else
{
if(s!=0)
ans.push_back(s);
vis[s]=1;
for(int i=1;i<=N+1;i++)
{
if(cri[s][i]!=INF&&vis[i]==0)
DFS(i,d);
}
vis[s]=0;
if(s!=0)
ans.pop_back();
}
}
三、总结
核心就是拓扑排序函数,根据这个函数可以得到ve,而ve是万恶之源,通过一对多的思想更新ve,通过多对一的思想更新vl。
ve和vl出来了,这就出来一大半了。el简直就是白给的。然后输出关键路径即可。
PS:代码如下:
#include<stdio.h>
#include<cstdio>
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<iostream>
#include<math.h>
#include<stack>
#include<algorithm>
#include<unordered_map>
using namespace std;
int INF=0x3f3f3f3f;
int G[1010][1010]={0};
int indegree[1010]={0};
int ve[1010]={0};//点的最早开始时间,找最大值
int vl[1010];//点的最迟开始时间,找最小值
int e[1010][1010]={0};//边的最早开始时间
int l[1010][1010]={0};//边的最迟开始时间
int cri[1010][1010];
//有向图是不用vis数组的,因为根本就回不去
//int vis[1010]={0};
vector<int> ans;
int vis[1010]={0};
int N,M;
void DFS(int s,int d)
{
if(s==d)
{
//ans.push_back(s);
int i;
for(i=0;i<ans.size()-1;i++)
{
printf("%d->",ans[i]);
}
printf("%d\n",ans[i]);
//ans.pop_back();
return;
}
else
{
if(s!=0)
ans.push_back(s);
vis[s]=1;
for(int i=1;i<=N+1;i++)
{
if(cri[s][i]!=INF&&vis[i]==0)
DFS(i,d);
}
vis[s]=0;
if(s!=0)
ans.pop_back();
}
}
stack<int> adtopo;
int topo(int s)
{
if(indegree[s]!=0)
return 0;
int num=0;
queue<int> q;
q.push(s);
adtopo.push(s);
ve[s]=0;
while(!q.empty())
{
int front=q.front();
//vis[front]=1;
num++;
adtopo.push(front);
q.pop();
for(int i=1;i<=N;i++)
{
if(G[front][i]!=INF)
{
indegree[i]--;
if(ve[i]<ve[front]+G[front][i])
ve[i]=ve[front]+G[front][i];
if(indegree[i]==0)
{
q.push(i);
}
}
}
}
if(num==N+1)
return 1;
else
return 0;
}
//大源点0大汇点N+1
int main()
{
scanf("%d %d",&N,&M);
fill(G[0],G[0]+1010*1010,INF);
fill(cri[0],cri[0]+1010*1010,INF);
for(int i=1;i<=M;i++)
{
int a,b,w;
scanf("%d %d %d",&a,&b,&w);
G[a][b]=w;
indegree[b]++;
}
vector<int> r,d;
for(int i=1;i<=N;i++)
{
if(indegree[i]==0)
{
//r=i;
r.push_back(i);
//break;
}
}
for(int i=0;i<r.size();i++)
{
G[0][r[i]]=0;
indegree[r[i]]++;
}
int flag=topo(0);
if(flag==0)
{
printf("NO\n");
return 0;
}
else
{
printf("YES\n");
//int d=adtopo.top();
int MAX=0;
for(int i=1;i<=N;i++)
{
if(ve[i]>MAX)
{
MAX=ve[i];
}
}
for(int i=1;i<=N;i++)
{
if(ve[i]==MAX)
d.push_back(i);
}
for(int i=0;i<d.size();i++)
{
G[d[i]][N+1]=0;
}
fill(vl,vl+1010,INF);
ve[N+1]=ve[d[0]];
vl[N+1]=ve[N+1];
for(int i=0;i<d.size();i++)
vl[d[i]]=vl[N+1];
//adtopo.pop();
while(!adtopo.empty())
{
int top=adtopo.top();
adtopo.pop();
for(int i=1;i<=N;i++)
{
if(G[top][i]!=INF)
{
if(vl[i]-G[top][i]<vl[top])
vl[top]=vl[i]-G[top][i];
}
}
}
for(int i=0;i<=N+1;i++)
{
for(int j=0;j<=N+1;j++)
{
if(G[i][j]!=INF)
{
e[i][j]=ve[i];
l[i][j]=vl[j]-G[i][j];
if(e[i][j]==l[i][j])
cri[i][j]=1;
}
}
}
int K;
scanf("%d",&K);
for(int i=0;i<K;i++)
{
int a,b;
scanf("%d %d",&a,&b);
printf("%d %d\n",e[a][b],l[a][b]);
}
printf("%d\n",ve[N+1]);
DFS(0,N+1);
}
}