一、概述
給出一個圖,首先判斷是否爲有向無環圖,如果是,則輸出要求的邊的最早發生時間和最晚發生時間,然後輸出所有關鍵路徑。
判斷是否爲有向無環圖,可以用拓撲排序來判斷。
二、分析
首先分析拓撲排序。使用鄰接矩陣儲存圖。
拓撲排序要用到的有:
入度數組,隊列。
入度數組判斷哪些元素入隊。
如下:
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);
}
}