<style> .reveal .slides { text-align: left; font-size:35px; } </style> # Shortest Path最短路徑 Introduction to Competitive Programming 2/16 --- ### 什麼是最短路徑? 通常會有一張圖,要你算從起點$s$走到終點$t$所需的最小時間or花費 ---- 像是4走到5的時間/花費是4 或是2走到3的時間/花費是7 ![image](https://hackmd.io/_uploads/ByHKSLqca.png =500x) ---- 圖也有分有向圖和無向圖 ![image](https://hackmd.io/_uploads/Hyb8UU9qp.png =500x) ---- 或是有負環或是沒負環 ![image](https://hackmd.io/_uploads/BJ0swLcqp.png =500x) 在最短路徑這個演算法上我們要對應不同的圖 使用合適的演算法 --- ## 複習存圖 - 鄰接矩陣 - 鄰接串列 ---- ## 鄰接矩陣 <div style="position: absolute; right: 0%; top:90%;"> ![](https://hackmd.io/_uploads/H1UbYU99p.png =400x) </div> <div style="position: absolute; left: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/Sk9MiIcqp.png =450x) </div> ---- ## 鄰接矩陣--存圖 - 有向 ```cpp! int dis[N][N]; void add_edge(int u,int v,int w){//起點u、終點v、權重w dis[u][v]=w; } ``` - 無向 ```cpp! int dis[N][N]; void add_edge(int u,int v,int w){//連接u和v、權重w dis[u][v]=w; dis[v][u]=w; } ``` ---- ## 鄰接串列 <div style="position: absolute; right: 0%; top:90%;"> ![](https://hackmd.io/_uploads/H1UbYU99p.png =400x) </div> <div style="position: absolute; left: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/BytUT8996.png =450x) </div> ---- ## 鄰接串列--存圖 - 有向 ```cpp! vector<pair<int,int>>graph[N];//pair存{終點,權重} void add_edge(int u,int v,int w){//起點u、終點v、權重w graph[u].push_back({v,w}); } ``` - 無向 ```cpp! vector<pair<int,int>>graph[N];//pair存{終點,權重} void add_edge(int u,int v,int w){//連接u和v、權重w graph[u].push_back({v,w}); graph[v].push_back({u,w}); } ``` --- ## 最短路徑分類 - 單源最短路徑(固定起點、不固定終點) - 全點對最短路徑(不固定起點、不固定終點) ---- ## 單源最短路徑 給你一個圖,一個起點,求出這個起點到每個點的最短路徑 ![](https://hackmd.io/_uploads/SJvzxGCqp.png =500x) ---- ## 全點對最短路徑 給你一個圖,多組起點和終點,求出每組起點到終點的最短路徑 ![image](https://hackmd.io/_uploads/SJfkEf0ca.png =600x) ---- ## 演算法們 - 單源最短路徑 - Dijkstra - Bellman-Ford - 全點對最短路徑 - Floyd-warshall --- ## Dijkstra ---- ## 想法 由於是單源最短路徑,所以會先創一個陣列,這裡叫dis 一開始會把dis[起點]設為0,代表起點到自己的距離是0 然後把dis[其他點]設為INF,再重複做下面的步驟 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ ---- ## 什麼是鬆弛 若圖中存在2個點$u$,$v$,並且存在一個連接$u,v$的邊,邊權為$w$,用dis[$u$]+$w$去取代dis[$v$],這種技巧就叫鬆弛 ---- 假設你要算dis(2,3),也就是2走到3的最短路徑 你走下面紅色那條邊之後,dis(2,3)會等於5 ![image](https://hackmd.io/_uploads/SkCsXNRca.png) ---- 一樣你也可以走上面,先走到1,使得dis(2,1)=1 ![image](https://hackmd.io/_uploads/HyAVNNR9T.png) ---- 那再來你會發現你可以走右邊那條邊從1走到3 顯然上面的路徑權重比下面的還要少 所以要用dis(2,1)+3取代掉dis(2,3) ![image](https://hackmd.io/_uploads/HkU2Fpksa.png =700x) ---- 所以鬆弛會寫成像這樣 若「起點到u的最短距離」加上「u到v這條邊的權重」小於「起點到v的最短距離」就更新答案 ```cpp! void relax(int u,int v,int w){//w是邊權權重 if(dis[u]+w<dis[v]){ dis[v]=dis[u]+w; } } ``` ---- 學會鬆弛之後我們就可以看看Dijkstra在做什麼了 假設一開始起點是2,要算下圖的最短距離 ![image](https://hackmd.io/_uploads/ByUNwERcT.png =600x) ---- ## 初始化 先把dis初始化,把起點設為0,其他設為INF(很大的數字) <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/ByE7D4Aqp.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | 0 | | 3 | | INF | 0 | | 4 | | INF | 0 | | 5 | | INF | 0 | | 6 | | INF | 0 | </div> ---- 1. `選一個離起點最近並且沒有走過的點u` $\to$ `選2` 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/BkXvoV0qa.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | 0 | | 3 | | INF | 0 | | 4 | | INF | 0 | | 5 | | INF | 0 | | 6 | | INF | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. `把點u設定為走過` $\to$ `visited[2]=1` 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/BkXvoV0qa.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | `1` | | 3 | | INF | 0 | | 4 | | INF | 0 | | 5 | | INF | 0 | | 6 | | INF | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. `透過所有連接點u和v的邊,去鬆弛點v` <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkMcjN05T.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | `1` | | 3 | | INF | 0 | | 4 | | ~~INF~~ `1` | 0 | | 5 | | INF | 0 | | 6 | | ~~INF~~ `7` | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/Hy7JaEA96.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | `1` | | 3 | | INF | 0 | | 4 | | 1 | 0 | | 5 | | INF | 0 | | 6 | | 7 | 0 | </div> ---- 1. `選一個離起點最近並且沒有走過的點u` $\to$ `選4` 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkQ7aVC96.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | `1` | | 3 | | INF | 0 | | 4 | | 1 | 0 | | 5 | | INF | 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. `把點u設定為走過` $\to$ `visited[4]=1` 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkQ7aVC96.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | INF | 0 | | 2 | | 0 | `1` | | 3 | | INF | 0 | | 4 | | 1 | `1` | | 5 | | INF | 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. `透過所有連接點u和v的邊,去鬆弛點v` <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkmIANRc6.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | ~~INF~~ `5` | 0 | | 2 | | 0 | `1` | | 3 | | ~~INF~~ `2` | 0 | | 4 | | 1 | `1` | | 5 | | ~~INF~~ `5`| 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/Hyn9R4Rca.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | 0 | | 4 | | 1 | `1` | | 5 | | 5 | 0 | | 6 | | 7 | 0 | </div> ---- 1. `選一個離起點最近並且沒有走過的點u` $\to$ `選3` 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/HJqU1HAcp.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | 0 | | 4 | | 1 | `1` | | 5 | | 5 | 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. `把點u設定為走過` $\to$ `visited[3]=1` 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/HJqU1HAcp.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 5 | 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. `透過所有連接點u和v的邊,去鬆弛點v` <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/S1pQeB0cp.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | ~~5~~ `4` | 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rJ6kbHC5p.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | 0 | | 6 | | 7 | 0 | </div> ---- 1. `選一個離起點最近並且沒有走過的點u` $\to$ `選5` 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkD4ZSRca.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | 0 | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. `把點u設定為走過` $\to$ `visited[5]=1` 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkD4ZSRca.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. `透過所有連接點u和v的邊,去鬆弛點v` <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/Bkx2-r0cp.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/r1O6GB0qa.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. `選一個離起點最近並且沒有走過的點u` $\to$ `選1` 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkEIXr0cT.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | 0 | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. `把點u設定為走過` $\to$ `visited[1]=1` 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkEIXr0cT.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | `1` | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. `透過所有連接點u和v的邊,去鬆弛點v` <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rkEIXr0cT.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | `1` | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. `選一個離起點最近並且沒有走過的點u` $\to$ `選6` 2. 把點$u$設定為走過 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rJqWEBA56.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | `1` | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | 0 | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. `把點u設定為走過` $\to$ `visited[1]=1` 3. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rJqWEBA56.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | `1` | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | `1` | </div> ---- 1. 選一個離起點最近並且沒有走過的點$u$ 2. 把點$u$設定為走過 3. `透過所有連接點u和v的邊,去鬆弛點v` <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/rJqWEBA56.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | `1` | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | `1` | </div> ---- 這樣就走完ㄌ <div style="position: absolute; right: 0%; top:90%;"> ![image](https://hackmd.io/_uploads/HyL_ES0qp.png =500x) </div> <div style="position: absolute; left: 0%; top:90%;"> | | | dis | visited | | --- | --- | --- | --- | | 1 | | 5 | `1` | | 2 | | 0 | `1` | | 3 | | 2 | `1` | | 4 | | 1 | `1` | | 5 | | 4 | `1` | | 6 | | 7 | `1` | </div> ---- 分析一下可能的複雜度,以下假設$V$為點個數,$E$為邊個數 1. 初始化 $\to$<!-- .element: class="fragment" data-fragment-index="1" --> $O(V)$<!-- .element: class="fragment" data-fragment-index="1" --> 2. 選一個離起點最近並且沒有走過的點$u$ $\to$<!-- .element: class="fragment" data-fragment-index="2" --> $O(V \cdot V)$<!-- .element: class="fragment" data-fragment-index="2" --> 3. 把點$u$設定為走過 $\to$<!-- .element: class="fragment" data-fragment-index="3" --> $O(V \cdot 1)$<!-- .element: class="fragment" data-fragment-index="3" --> 4. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ $\to$<!-- .element: class="fragment" data-fragment-index="4" --> $O(E)$<!-- .element: class="fragment" data-fragment-index="4" --> 總複雜度:<!-- .element: class="fragment" data-fragment-index="5" -->$O(V^2+E)$<!-- .element: class="fragment" data-fragment-index="5" --> ---- ### 來看這個[例題](https://cses.fi/problemset/task/1671) 給你一個圖,圖上有$V$個點,$E$條邊,給定起點問你這個起點到每個點的最短距離。 $1 \le V\le10^5$,$1 \le E\le 2\cdot 10^5$ ---- 顯然剛剛的做法砸下去就會過了,那複雜度呢? $O(V^2+E)$<!-- .element: class="fragment" data-fragment-index="1" --> $\to$<!-- .element: class="fragment" data-fragment-index="1" --> $O(10^{10})$<!-- .element: class="fragment" data-fragment-index="1" --> $\to$<!-- .element: class="fragment" data-fragment-index="1" --> TLE<!-- .element: class="fragment" data-fragment-index="1" --> ---- 看看有哪些步驟的複雜度是可以被優化掉的 1. 初始化 $\to$ $O(V)$ 2. 選一個離起點最近並且沒有走過的點$u$ $\to$ $O(V \cdot V)$ 3. 把點$u$設定為走過 $\to$$O(V \cdot 1)$ 4. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ $\to$ $O(E)$ 總複雜度:$O(V^2+E)$ 由於$V^2$太大,所以我們可以想辦法壓掉第二個步驟的複雜度 ---- 2. `選一個離起點最近並且沒有走過的點u` 你會發現你是要選一個點$u$,且dis[$u$]盡可能越小越好 大家可以想一下要怎麼樣比$O(V)$還要快選到這個點$u$ ---- 可以搭配priority_queue這個STL工具來用 你可以把每個有被鬆弛過的點x套成一個pair,(dis[x],x)丟進priority_queue裡面,裡面會按照dis去做排序 他可以幫你用$O(logV)$的時間知道哪個$u$的dis[$u$]是最小的 ---- 這時候的複雜度就會變成這樣 1. 初始化 $\to$ $O(V)$ 2. 選一個離起點最近並且沒有走過的點$u$ $\to$ $O(V \cdot logV)$ 3. 把點$u$設定為走過 $\to$$O(V \cdot 1)$ 4. 透過所有連接點$u$和$v$的邊,去鬆弛點$v$ $\to$ $O(E)$ 總複雜度:$O(VlogV+E)$ TLE就解決了 ---- 範例code: ```cpp! [|6-9|10-11|13-14|15-16|17-22|] #define pii pair<int,int> #define INF 1e18 vector<pair<int,int>>vec[N]; void dijkstra(int s,int t){//起點,終點 int dis[N]; for(int i=0;i<N;i++){//初始化 dis[i]=INF;//值要設為比可能的最短路徑權重還要大的值 } dis[s]=0; priority_queue<pii,vector<pii>,greater<pii>>pq;//以小到大排序 pq.push({dis[s],s}); while(pq.empty()==0){ int u=pq.top().second; pq.pop(); if(vis[u])continue; vis[u]=1; for(auto [v,w]:vec[u]){ if(dis[u]+w<dis[v]){//鬆弛 dis[v]=dis[u]+w; pq.push({dis[v],v}); } } } } ``` 複雜度:$O(VlogV+E)$ ---- ## 小限制 如果圖中有負權邊,Dijkstra這個演算法不能用 因為Dijkstra會取dis最小的點,在有負權邊的情況下會爛掉 ---- 假設其他點都已經跑完了,剩下點6要跑一次 他可以用最下方權重為-10的邊去更新2 之後甚至也可以用點2再重新更新點4,因此這個演算法會爛掉 ![image](https://hackmd.io/_uploads/B121tIA5a.png =500x) ---- ## 最短路徑樹 當你給定了一個起點,從這個起點走到任意的點的路徑會形成一棵樹(也有可能是多顆) 以下面的例子來說,最短路徑樹有兩顆 ![image](https://hackmd.io/_uploads/Sy7kFnJop.png) --- ## 下課時間 好好消化剛剛剛教的內容 --- ## Bellman-Ford ---- ## 想法 Bellman-Ford是以每一條邊去做鬆弛,能鬆弛就直接去做鬆弛,直到不能鬆弛為止。 ![image](https://hackmd.io/_uploads/SJJ5Tmyjp.png =600x) ---- 不難發現由於一條最短路最多會經過$n-1$條邊,因此只要對每個邊鬆弛$n-1$次就好。 ![image](https://hackmd.io/_uploads/SJJ5Tmyjp.png =600x) ---- 以每一條邊去做鬆弛 ```cpp! for(int i=0;i<m;i++){ if(dis[edge[i].u]+edge[i].w<dis[edge[i].v]){ dis[edge[i].v]=dis[edge[i].u]+edge[i].w; } } ``` 複雜度:總共$m$條邊,鬆弛$n-1$次 $\to O(nm)$ ---- 可以知道在進行$n-1$次對全部的邊做鬆弛之後會出現最短路徑,那如果是這張圖呢 ![image](https://hackmd.io/_uploads/H1fflNksT.png) ---- 以下是進行$n-1$次鬆弛的過程,你會發現在有負環的情況下他會爛掉 ![image](https://hackmd.io/_uploads/HJFglNJip.png) ---- ## 判斷負環 因此你可以考慮在第n次做一次鬆弛,如果有點被鬆弛到,代表這張圖存在負環 --- ## Floyd-warshall ---- ## 想法 對於一個點對(i,j),我要找i走到j的最短路徑,可能會存在一個中繼點k。 這時候從i走到k加上k走到j的路徑可能會更短 因此這時候窮舉每個點k當作中繼點去更新答案 就可以更新所有最短路徑 ---- `dis[i][j]`指的是從i走到j的最短路徑 `dis[1][3]`就代表從1走到3的最短路徑,一開始可能是無限大 可以找到一個中繼點2,這時候`dis[1][2]=1`且`dis[2][3]=5`,相加起來比`dis[1][3]`小 因此可以用來更新`dis[1][3]` ![image](https://hackmd.io/_uploads/ryjMHdJja.png) ---- ## 初始化和讀邊 - 自己到自己的距離為0,否則為INF - 有一個有向邊為u走到v,權重為w,更新dis[u][v] ```cpp! [1-7|8-10] int dis[N+1][N+1]; for(int i=1;i<=N;i++){ for(int j=1;j<=N;j++){ dis[i][j]=1e18; } dis[i][i]=0; } int u,v,w; cin>>u>>v>>w; dis[u][v]=min(dis[u][v],w); ``` 初始化時間複雜度:$O(N^2)$ ---- 依序枚舉每個中繼點,再枚舉每個點對,順序是(k,i,j) ```cpp! for(int k=1;k<=N;k++){//窮舉中繼點k for(int i=1;i<=N;i++){ for(int j=1;j<=N;j++){//窮舉點對(i,j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); } } } ``` 時間複雜度:$O(N^3)$ ---- Floyd-warshall可以在時間複雜度$O(N^3)$、空間複雜度$O(N^2)$以內做完全點對最短路徑問題 基本上要做全點對最短路徑一定砸Floyd-warshall --- 來練習8 [題目連結](https://vjudge.net/contest/603684#overview)
{"title":"Shortest Path","description":"Introduction to Competitive Programming2/16","contributors":"[{\"id\":\"08326fa4-ca9b-4ca9-873e-239ebe76662c\",\"add\":20641,\"del\":914},{\"id\":\"19f09ccf-6b99-452f-971f-955cfc1657f3\",\"add\":1,\"del\":1},{\"id\":\"f252a272-5088-4254-be0e-814b97d3ed17\",\"add\":8,\"del\":8}]"}
    1072 views
   Owned this note