題目傳送門
做完這道題後感覺對 Floyd 的理解更深了。
根據題面要求,設 \(f(k, i, j)\) 表示從 \(i\) 到 \(j\) 的所有只經過 \(1\sim k\) 的點的所有路徑的最短距離。
很明顯 \(k\) 那一維是階段,因為它描述了從 \(i\) 到 \(j\) 路徑中的不同點,而我們就是根據這一條件來劃分集合,這也是為什麼 \(k\) 必須放在最外層迴圈。
所以狀態轉移方程為:
\[f(k, i, j) = \min\limits_{1\le i,j\le n}\{f(k - 1, i, k) + f(k - 1, k, j)\}
\]
由於第 \(k\) 層只會用到第 \(k - 1\) 層的狀態,所以可以加滾動陣列最佳化。
和揹包的狀態轉移方程就相似,還可以直接將這一維省去,因為在外層迴圈到 \(k\) 時,\(f(i, k)\) 恰好等於 \(f(k - 1, i, k)\),\(f(k, j)\) 同理。
再特判一個走不到的情況,即 \(f(k, i, j) = \infty\),然後全加起來就好了。
時間複雜度為 \(O(n^3)\)。
\(\texttt{Code:}\)
#include <iostream>
using namespace std;
const int N = 410, inf = 0x3f3f3f3f;
int n, m;
int g[N][N];
long long res;
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i != j) g[i][j] = inf;
int a, b, w;
for(int i = 1; i <= m; i++) {
scanf("%d%d%d", &a, &b, &w);
g[a][b] = min(g[a][b], w); //防重邊
}
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++) {
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
if(g[i][j] < inf) res += g[i][j];
}
printf("%lld", res);
return 0;
}