題目描述
一個無環的有向圖稱為無環圖(Directed Acyclic Graph),簡稱DAG圖。
AOE(Activity On Edge)網:顧名思義,用邊表示活動的網,當然它也是DAG。與AOV不同,活動都表示在了邊上,如下圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SO5MTO2AzM4kTZhJmZ3YzYyYzX0UTOzQTM3AzLcdDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
如上所示,共有11項活動(11條邊),9個事件(9個頂點)。整個工程隻有一個開始點和一個完成點。即隻有一個入度為零的點(源點)和隻有一個出度為零的點(彙點)。
關鍵路徑:是從開始點到完成點的最長路徑的長度。路徑的長度是邊上活動耗費的時間。如上圖所示,1 到2 到 5到7到9是關鍵路徑(關鍵路徑不止一條,請輸出字典序最小的),權值的和為18。
Input
這裡有多組資料,保證不超過10組,保證隻有一個源點和彙點。輸入一個頂點數n(2<=n<=10000),邊數m(1<=m <=50000),接下來m行,輸入起點sv,終點ev,權值w(1<=sv,ev<=n,sv != ev,1<=w <=20)。資料保證圖連通。
Output
關鍵路徑的權值和,并且從源點輸出關鍵路徑上的路徑(如果有多條,請輸出字典序最小的)。
輸入樣例
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
思路:
這個題的意思是讓求源點和彙點的關鍵路徑并輸出,當有多條時,輸出字典序最小的那個.
求最長路徑,可以将權值加負号來求解,也可以改變松弛條件,距離大的更新.而Dijkstra算法無法處理負權邊,也沒有辦法通過改變松弛條件來求解最長路(因為它用到了貪心的思想).求解最長路有一下兩種方法:
- 按照拓撲序列求解最長路.
- 按照Bellman或SPFA算法,改變松弛條件,求解最長路.
但由于Bellman算法時間複雜度是O( n * m),可能會逾時,是以為了減少複雜度需要使用SPFA算法,該複雜度是O(k * m),k是一個很小的數,最多為O(n*m)
其次由于要輸出路徑,而且路徑要按照字典序選取,是以反向建圖會更友善的輸出(如果正向建圖則需要借助于棧來進行輸出,如果資料量大,可能通不過)
參考代碼
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
using namespace std;
const int maxv = 10000+10,maxe = 50000+10,INF = 0xc0c0c0c0;
int head[maxv],dis[maxv],p[maxv],in[maxv],out[maxv],vis[maxv],num,n,m;
struct Edge{
int next,to,w;
}e[maxe];
void add(int u,int v,int w){
e[++num].to = v;
e[num].w = w;
e[num].next = head[u];
head[u] = num;
}
void SPFA(int u){
for(int i = 1; i <= n; i++){
dis[i] =INF;
}
queue<int> q;
q.push(u);
vis[u] = 1;
dis[u] = 0;
p[u] = -1;
while(!q.empty()){
int x = q.front();
q.pop();
vis[x] = 0;
for(int i = head[x]; i ;i = e[i].next){
if(dis[e[i].to]<dis[x]+e[i].w || (dis[e[i].to]==dis[x]+e[i].w&&p[e[i].to] > x)){
dis[e[i].to] = dis[x]+e[i].w;
p[e[i].to] = x;
if(!vis[e[i].to]){//如果沒有通路過就入隊列.
q.push(e[i].to);
vis[e[i].to] = 1;
}
}
}
}
}
int main()
{
stack<int> stack1;
int u,v,w,s,t,k;
while(cin>>n>>m){
//初始化
memset(head,0,sizeof(head));
memset(dis,0xc0,sizeof(head));
memset(p,-1,sizeof(p));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
memset(vis,0,sizeof(vis));
num = 0;
for(int i = 1;i <= m; i++){
cin>>v>>u>>w;
add(u,v,w);
in[v]++;
out[u]++;
}
for(int i = 1;i <= n; i++){
if(in[i]==0){
s = i;
}
if(out[i]==0){
t = i;
}
}
SPFA(s);
cout<<dis[t]<<endl;
k = t;
while(p[k]!=-1){//這裡必須進行反向建邊. 按說正向+棧也可以,但運作卻不行.可能時間複雜度太高了.如果逆向建邊,則直接從前往後輸出即可,不需要使用棧.
cout<<k<<" "<<p[k]<<endl;
k = p[k];
}
// while(p[k]!=-1){
// stack1.push(k);
// stack1.push(p[k]);
// k = p[k];
// }
// while(!stack1.empty()){
// //stack1.top();
// cout<<stack1.top()<<" ";
// stack1.pop();
// cout<<stack1.top()<<endl;
// stack1.pop();
// }
}
return 0;
}
參考代碼2(Bellman-Ford)
#include<iostream>
#include<string.h>
using namespace std;
const int maxe = 50000+10,maxn = 10000+10;
struct node{
int u,v,w;
}e[maxe]; //邊集數組
int dis[maxn],p[maxn],in[maxn],out[maxn],n,m,s,t;
void Bellman_Ford(){
int temp = 0;
for(int i = 0;i < n-1; i++){//n-1次松弛
temp = 0;
for(int j = 1; j <= m; j++){
if((dis[e[j].v] < dis[e[j].u]+e[j].w)||(dis[e[j].v] == dis[e[j].u]+e[j].w &&p[e[j].v] > e[j].u)){//如果①路徑更大 ②路徑長度相等,但前驅節點可以更小.滿足任意一個都可進行更新.
dis[e[j].v] = dis[e[j].u] + e[j].w;
p[e[j].v] = e[j].u;
temp = 1;
}
}
if(!temp){//如果某一輪,沒有發生更新則說明已跟新完畢.
break;
}
}
}
int main()
{
int u,v,w,cnt;
while(cin>>n>>m){
//資料初始化
memset(dis,0,sizeof(dis));
memset(p,-1,sizeof(p));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
cnt = 0;
for(int i = 1; i <=m; i++){
cin>>v>>u>>w;//建立反向圖
e[++cnt].u = u;
e[cnt].v = v;
e[cnt].w = w;
in[v]++;
out[u]++;
}
for(int i = 1; i <= n; i++){
if(!in[i]){
s = i;
}
if(!out[i]){
t = i;
}
}
Bellman_Ford();
cout<<dis[t]<<endl;
int k = t;
//輸出關鍵路徑
while(p[k]!=-1){
cout<<k<<" "<<p[k]<<endl;
k = p[k];
}
}
return 0;
}