1. 程式人生 > 其它 >經過指定點的最短路徑

經過指定點的最短路徑

題目描述:

給出一個有 n 個頂點的有向網,指定其中 k 個頂點( 不含頂點 1 和頂點 n ),求從頂點 1 到頂點 n 的,經過那 k 個頂點的最短路。

輸入:

第一行是 頂點數 n 和 弧數目 e 。 1 ≤ n ≤ 40 ,1 ≤ e ≤ n × n
接下來是 e 行,每行三個整數 i , j , v ( 其中 0 < i, j ≤ n , 0 < v ≤ 100 ) ,表示從 頂點 i 到 頂點 j 的一條權值為 v 的弧。
接下來是一個正整數 k ( 1 ≤ k ≤ n - 2 ),接下來是 k 個正整數,表示必須經過的 k 個頂點。

輸出:

如果不存在滿足條件的路徑,輸出一行 "No solution";
否則,輸出兩行:
第 1 行,該最短路的長度;
第 2 行從頂點 1 到頂點 n 的最短路,頂點之間用一個空格分隔,要求按路徑的頂點次序,前一個頂點必須有弧指向後一個頂點

演算法思路:暴力列舉所有滿足條件的路徑,計算路徑長度並不斷更新得到最短路。

演算法實現:

1.跑一遍Floyd,求各頂點之間的最短路徑

2.對所有指定點進行全排列,對每個排列計算:頂點1到序列首頂點的距離+序列中相鄰頂點之間的距離+序列末頂點到頂點n的距離,並不斷更新得到最短路徑

3.特判不存在滿足路徑的情況

#include<bits/stdc++.h>
using namespace std;
 
#define INF 0x3f3f3f3f //設無窮大值
 
int n, m, k, idx;
int dist[45][45];   //儲存各頂點間的最短路
int path[45
][45]; //用於floyd儲存路徑 int must[40]; //儲存指定點序列 char temp[45], anspath[45]; void floyd() { for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (dist[i][j] > dist[i][k] + dist[k][j]) { dist[i][j]
= dist[i][k] + dist[k][j]; path[i][j] = k; } } //獲取點a到點b最短路的中間路徑(路徑中不含a,b) void getpath(int a, int b) { if (path[a][b] == -1) return; else { int k = path[a][b]; getpath(a, k); anspath[idx++] = k; getpath(k, b); } } //將“點1沿著當前must陣列中指定點的順序走到點n的路徑”儲存到anspath陣列 void savpath() { anspath[idx++] = 1; getpath(1, must[0]); for (int i = 0; i < k - 1; i++) { anspath[idx++] = must[i]; getpath(must[i], must[i + 1]); } anspath[idx++] = must[k - 1]; getpath(must[k - 1], n); anspath[idx++] = n; } //返回 點1沿著當前must陣列中指定點的順序走到點n 的路徑長度 int getdis() { int distance = dist[1][must[0]]; if (dist[1][must[0]] == INF) return INF + 1; for (int i = 0; i < k - 1; i++) { if (dist[must[i]][must[i + 1]] == INF) return INF + 1; distance += dist[must[i]][must[i + 1]]; } if (dist[must[k - 1]][n] == INF) return INF + 1; distance += dist[must[k - 1]][n]; return distance; } int main() { memset(dist, 0x3f, sizeof dist); memset(path, -1, sizeof path); scanf("%d%d", &n, &m); int a, b, w; while (m--) { scanf("%d%d%d", &a, &b, &w); dist[a][b] = min(dist[a][b], w); } floyd(); scanf("%d", &k); for (int i = 0; i < k; i++) scanf("%d", &must[i]); //判斷點1能否走到點n if (dist[1][n] == INF) { printf("No solution\n"); return 0; } //使用next_permutation函式前先對陣列進行排序 sort(must, must + k); int mindis = INF; do { int d = getdis(); if (mindis > d) {//如果存在更短的路徑則更新答案,並把路徑存到anspath[] mindis = d; idx = 0; savpath(); } } while (next_permutation(must, must + k)); printf("%d\n", mindis); for (int i = 0; i < idx; i++) printf("%d ", anspath[i]); return 0; }

本題的一個坑:使用dfs演算法無法通過。

如果通過使用dfs演算法記錄所有點1到達點n並且經過指定點的路徑,在其中取最小值並不正確。因為dfs演算法要求路徑中的點最多隻出現一次,但經過指定點的最短路的路徑中可能存在同一個點出現多次的情況,而這種情況恰好被dfs排除,因此本題不用dfs。

例:在如下圖中,求點1經過點2,點3到達點4的最短路徑和長度

如果我們用dfs求解,得到的最短路徑為:1->2->3->4,長度為420

而用暴力方法得到的最短路答案為:1->2->3->2->4,長度為40,其中點2在路徑中出現過兩次

顯然,後者所求的最短路徑正確。