By 王政祺
來試試以下這些名詞吧
前提: 不能有負環!!!
最短路徑長度最大\(v-1\),若鬆弛\(v-1\)次後還能再鬆弛就表示存在負環
tf = 0; // tf 表示是否有負環
dis[1] = 0;
for (int i = 2; i <= N; i++) dis[i] = INF; // 起點到 i 的最短距離,初始設為無限大
for (int i = 1; i < N; i++) { // 跑 v-1 次
for (int j = 0; j < M; j++) { // 窮舉邊
a = path[j].ff, b = path[j].ss;
if (dis[a] != INF && dis[a] + c[j] < dis[b]) { // 如果可以 relax 的話
dis[b] = dis[a] + c[j];
}
}
}
for (int i = 0; i < M; i++) {
a = path[i].ff, b = path[i].ss;
if (dis[a] + c[i] < dis[b]) { // 如果還能 relax 的話就表示有負環
tf = 1;
}
}
來一點優化吧
如果沒有負邊的話...?
子節點的最短路徑在沒有負邊的情況下一定不比父節點小
vector < pii > path[N+1]; // adjacency list
priority_queue < pii , vector < pii >, greater < pii > > pq;
pii cur;
fill(dis, dis+N, INF);
dis[1] = 0;
pq.push({0, 1});
//每 個 節 點 只 會 被 更 新 一 次
for(int i = 0; i < N; i++){
//將 已 更 新 的 節 點 從 清 單 移 除(Lazy Deletion)
do cur = pq.top(), pq.pop();
while(cur.ff > dis[cur.ss]);
for(auto e : path[cur.ss]) // Relax
if(dis[e.ss] > cur.ff+e.ff)
dis[e.ss] = cur.ff+e.ff,
pq.push({dis[e.ss], e.ss});
}
用 Dijkstra 的話需要 \(O(V*(V+E) log E)\)
感覺有點糟QQ
有沒有一種 DP 的感覺
把「由 \(i\) 點中途經過前 \(k\) 點抵達 \(j\) 點的最短路徑」作為 DP 狀態
\(dp_{i,j,k}= min(dp_{i,j,k-1}, dp_{i,k,k-1}+dp_{k,j,k-1})\)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dis[i][j] = INF; // 初始化
if(i==j) dis[i][j] = 0;
}
}
//cin
for (int k = 1; k <= n; k++)// 窮舉 k 來看看能不能 Relax dis(i, j)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (dis[i][k] + dis[k][j] < dis[i][j])
dis[i][j] = dis[i][k] + dis[k][j];
一張有邊權的連通無向圖
求一個邊權總和最小的生成樹
有點玄?但還算可以理解嗎(?
加上連接兩者之中最小的那條邊就對了
反正不管怎樣挑最短的那條來連就對了
每次找當前邊權最小的邊,檢查他的兩邊是否在同一個集合(生成樹)中
不在的話,就把它連起來吧
所以說有人記得曾經有個叫做並查集的東西嗎?
int dsu[MAXN+5];
int qry(int now) {
if (dsu[now] != now) dsu[now] = qry(dsu[now]);
return dsu[now];
}
void Union(int a, int b) {
dsu[qry(a)] = qry(b);
}
sort(path, path+m, cmp); // 讓 path 陣列依照邊權值大小排序
for (int i = 0; i < m; i++) {
if (qry(path[i].a) == qry(path[i].b)) continue;
ans += path[k].c; // 將答案加上權重
Union(path[k].a, path[k].b);
}
其實就是類似 dijkstra 啦
最短路徑樹 ~ 最小生成樹
把記錄的當前最短路徑改為「與當前最小生成樹的距離」