天天看點

SDUTOJ2498---AOE網上的關鍵路徑

​​題目描述​​

一個無環的有向圖稱為無環圖(Directed Acyclic Graph),簡稱DAG圖。

AOE(Activity On Edge)網:顧名思義,用邊表示活動的網,當然它也是DAG。與AOV不同,活動都表示在了邊上,如下圖所示:

SDUTOJ2498---AOE網上的關鍵路徑

如上所示,共有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算法無法處理負權邊,也沒有辦法通過改變松弛條件來求解最長路(因為它用到了貪心的思想).求解最長路有一下兩種方法:

  1. 按照拓撲序列求解最長路.
  2. 按照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;
 }      

繼續閱讀