图论 —— DAG 图的最长路
【概述】
DAG 圖的最長路問題是一個比較少見的問題,具體問題是:給出一個 DAG 圖,尋找圖中的最長路
在 AOE 網中,在找出關鍵路徑后,對其進行 DFS 即可得到圖的最長路,由于這種方法的實現過于繁瑣,這里介紹幾種較為簡單的實現。
【最短路算法】
對于最短路算法,Floyd,Dijkstra、Bellman-Ford、SPFA 等,將其松弛操作進行修改,即可將最短路算法變為最長路算法。
以 Floyd 為例:
int G[N][N]; void Floyd(){for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=k&&j!=i&&j!=k)if(g[i][j]<g[i][k]+g[k][j])g[i][j]=g[i][k]+g[k][j]; } int main(){int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&g[i][j]);Floyd();int res=-INF;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)res=max(g[i][j],res);printf("%d\n",res);return 0; }【拓撲排序】
在拓撲排序的過程中,不斷記錄路徑,最后對路徑進行排序,輸出最大的那個即為 DAG 的最長路
struct Node{int to,dis;Node(){}Node(int to,int dis):to(to),dis(dis){} }; vector<Node> G[N]; int in[N]; int dis[N]; int n,m; void topSort() {stack<int > S;for(int i=1; i<=n; i++)if(!in[i])S.push(i);while(!S.empty()) {int x=S.top();S.pop();for(int j=0; j<G[x].size(); j++) {int y=G[x][j].to;dis[y]=max(dis[y],dis[x]+G[x][j].dis);in[y]--;if(!in[y])S.push(y);}} }int main() {int T;scanf("%d",&T);while(T--){scanf("%d%d",&n,&m);memset(in,0,sizeof(in));memset(dis,0,sizeof(dis));for(int i=0; i<=n; i++)G[i].clear();for(int i=1; i<=m; i++) {int x,y,dis;scanf("%d%d%d",&x,&y,&dis);Node temp;temp.to=y;temp.dis=dis;in[y]++;G[x].push_back(temp);}topSort();int res=-INF;for(int i=1;i<=n;i++)res=max(res,dis[i]);printf("%d\n",res);}return 0; }【動態規劃】
1.不固定終點起點
當給定一個 DAG 圖時,要求整個圖中所有路徑中權值和最大的那條,即不固定終點和起點問題。
設 dp[i] 為從 i 點出發能獲得的最長路徑長度,G[i][j] 為從 i 點到 j 點的距離,這樣所有的 dp[i] 的最大值就是整個 DAG 的最長路徑長度,如果從 i 點出發,能直接到達頂點 j1、j2、...、jk,而 dp[j1]、dp[j2]、...、dp[k] 均已知,那么有:dp[i]=max{ dp[j]+G[i][j] }
根據上面的思路,由于最后的頂點沒有出邊,因此需要按照逆拓撲排序來求解 dp 數組,但可以利用遞歸來進行求解:由于從出度為 0 的頂點出發的最長路徑長度為 0,因此邊界就是這些點,在具體實現中不妨對整個 dp 數組初始化為 0,這樣 DP 函數當前訪問頂點i的出度為0時就會直接返回 dp[i]=0,而出度不為 0 的時候就會遞歸求解,遞歸過程中遇到已經計算過的頂點則直接返回對于的 dp 值,于是從邏輯上實現了逆拓撲排序的效果。
其基于鄰接矩陣實現的代碼如下:
int dp[N];//使用前整個數組設為0 int G[N][N]; int DP(int i) {if(dp[i]>0)return dp[i];for(int j=0; j<n; j++)//遍歷i的所有可達出邊if(G[i][j]!=INF)dp[i]=max(dp[i],DP(j)+G[i][j]);return dp[i]; }當需要輸出這條最長路時,可以利用一個 next 數組來記錄 i 頂點的后繼結點,再求完最長路后,順序打印路徑即可
int DP(int i) {if(dp[i]>0)return dp[i];for(int j=0; j<n; j++) { //遍歷i的所有可達出邊if(G[i][j]!=INF) {int temp=DP(j)+G[i][j];//單獨計算dpif(dp[i]<temp) { //可以獲得更長的路徑dp[i]=temp;next[i]=j; //保存i的后繼頂點j}}}return dp[i]; } void printPath(int i) {//調用前需先獲得最大的dp[i],然后將i作為路徑的起點傳入printf("%d",i);while(next[i]!=-1) { //next數組初始化為-1i=next[i];printf("->%d",i);}printf("\n"); }2.固定終點起點
給定一個 DAG 圖,給出一個起點和終點,要求從起點到終點的路徑中權值和最大的那條,即固定終點和起點問題。
設規定的終點為 T,那么設 dp[i] 為從 i 號點出發到達終點 T 所能獲得的最大長度,G[i][j] 為從 i 點到 j 點的距離,如果從 i 點出發,能直接到達頂點 j1、j2、...、jk,而 dp[j1]、dp[j2]、...、dp[k] 均已知,那么有:dp[i]=max{ dp[j]+G[i][j] }
可以發現,這個 dp 式子與上面不固定終點起點的問題相同,但問題的區別在于邊界:
第一個問題中,沒有固定的終點,因此邊界為所有出度為 0 的頂點,其?dp 值為 0
第二個問題中,固定了終點,因此邊界應當為 dp[T]=0,需要注意的是,由于從某頂點出發可能會無法到達終點 T,因此此時 dp 數組不能再全部初始化為 0,比較合適的做法是將 dp 初始化為一個極大的負數(-INF),來表達無法到達終點,然后設置一個 vis 數組來表示頂點是否已被訪問
int vis[N]; int G[N][N]; int dp[N];//使用前初始化為-INF,且終點dp[T]=0 int DP(int i) {if(vis[i]) return dp[i];vis[i]=true;for(int j=0; j<n; j++) { //遍歷i的所有可達出邊if(G[i][j]!=INF) {dp[i]=max(dp[i],DP(j)+G[i][j]);}}return dp[i]; }【例題】
- Find the safest road(HDU-1596)(Floyd 變形求最長路):點擊這里
- 矩形嵌套(NYOJ-16)(dp 求最長路):點擊這里
- Skiing(2017 ACM-ICPC 亞洲區(烏魯木齊賽區)網絡賽 H)(拓撲排序求最長路):點擊這里
總結
以上是生活随笔為你收集整理的图论 —— DAG 图的最长路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Uim的情人节礼物·其之弐(洛谷-P25
- 下一篇: Cow Line(洛谷-P3014)