1. 程式人生 > 其它 >ACM - 最短路 - CodeForces 295B Greg and Graph

ACM - 最短路 - CodeForces 295B Greg and Graph

CodeForces 295B Greg and Graph

題解

\(Floyd\) 演算法是一種基於動態規劃的演算法,以此題為例介紹最短路演算法中的 \(Floyd\) 演算法。

我們考慮給定一個圖,要找出 \(i\) 號點到 \(j\) 號點的最短路徑。則該最短路徑只有兩種可能:

  • \(i\) 號點直接到達 \(j\) 號點的路徑中產生最短路徑

  • \(i\) 號點經過一些中間點到達 \(j\) 號點的路徑中產生最短路徑

我們新增一個點 \(k\),使得 \(i\) 號點到 \(j\) 號點新增後產生的最短路徑比新增前的最短路徑更短,則稱該操作為鬆弛

下面我們以從一個示例開始介紹演算法的步驟。

示例

給定一個賦權有向圖,如下圖:

演算法步驟

我們宣告一個集合 \(S\),並稱該集合為中轉集合

我們宣告一個 \(dist\) 陣列,其中 \(dist[i][j]\) 表示從源點 \(i\) 號點只經過中轉集合中的點,到達目標點 \(j\) 號點的最短路徑。

每一輪更新都更新 \(dist\),直到集合 \(S\) 為圖的頂點集 \(V\)

初始化更新

\(S\) 初始化為:

\[S = \{ \} \]

\(dist\) 陣列根據我們的定義,此時 \(dist[i][j]\) 表示不經過圖中的任何頂點,直接從源點 \(i\) 號點到達 \(j\) 號點的最短距離,為了方便書寫,我們此處採用圖的鄰接矩陣表示。

\[dist[i][j] = graph[i][j] \quad \forall i,j \in V \]

\(dist\) 被初始化為:

\[dist = \begin{bmatrix} 0 & 5 & 10 & inf & inf \\ inf & 0 & inf & 5 & 15 \\ inf & 30 & 0 & 20 & inf \\ 10 & inf & inf & 0 & 25 \\ 40 & inf & inf & inf & 0 \end{bmatrix} \]

第一輪更新

\(S\) 初始化為:

\[S = \{ 0 \} \]

\(dist\) 陣列根據我們的定義,此時 \(dist[i][j]\) 表示由圖中 \(0\) 號點中轉,直接從源點 \(i\) 號點到達 \(j\) 號點的最短距離。該最短路徑有兩種可能:

  1. 最短路徑經過 \(0\) 號點;
  2. 最短路徑不經過 \(0\) 號點;

對於第 \(1\) 種情況說明 \(0\) 號點可以鬆弛之前的最短距離。這兩種情況可以統一表示為下式:

\[\begin{align} dist[i][j] = \min \left( dist[i][j],dist[i][0] + dist[0][j] \right) \quad \forall i,j \in V \tag{1} \end{align} \]

此處我們應該注意一下更新的順序問題,上式表達的含義應該是指只由上一輪的 \(dp[i][j]\)\(dp[i][0]\)\(dp[0][j]\) 來更新出這一輪的 \(dp[i][j]\)。更準確地說應該可以擴充套件下陣列 \(dist\) 的維度。 如下圖所示:

其中 \(k\) 表示這是第 \(k\) 輪更新。按我們的意思,應該寫成:

\[dist[i][j][1] = \min \left( dist[i][j][0],dist[i][0][0] + dist[0][j][0] \right) \quad \forall i,j \in V \]

即只由第 \(0\) 輪的 \(dp[i][j]\)\(dp[i][0]\)\(dp[0][j]\) 來更新出第 \(1\) 輪的 \(dp[i][j]\)。那我們為什麼可以寫成式 \(1\) 的樣子?原因在於 \(dp[i][0]\)\(dp[0][j]\) 在此輪更新迴圈中,始終都沒有被更新。

\(dist\) 被初始化為:

\[dist = \begin{bmatrix} 0 & 5 & 10 & inf & inf \\ inf & 0 & inf & 5 & 15 \\ inf & 30 & 0 & 20 & inf \\ 10 & 15* & 20* & 0 & 25 \\ 40 & 45* & 50* & inf & 0 \end{bmatrix} \]

第二輪更新

\(S\) 初始化為:

\[S = \{ 0, 1 \} \]

\(dist\) 陣列根據我們的定義,此時 \(dist[i][j]\) 表示從源點 \(i\) 號點出發,只經過中轉集合 \(S\) 中的點,到達 \(j\) 號點的最短距離。該最短路徑有兩種可能:

  1. 最短路徑經過 \(1\) 號點;
  2. 最短路徑不經過 \(1\) 號點;

對於第 \(1\) 種情況說明 \(1\) 號點可以鬆弛之前的最短距離。這兩種情況可以統一表示為下式:

\[dist[i][j] = \min \left( dist[i][j],dist[i][1] + dist[1][j] \right) \quad \forall i,j \in V \]

我們接著就要問了,為什麼經過 \(1\) 號點的最短路徑距離等於 \(dist[i][1] + dist[1][j]\)。在第一輪更新的時候容易想到,但這輪更新(第二輪更新)中,該問題需要仔細思考一下。

經過 \(1\) 號點的路徑這樣表示:\(i\)(源點) \(\to\) \(1\) \(\to\) \(j\)(目標點)。而經過 \(1\) 號點的最短路徑一定是由 \(i\)\(1\) 的最短路徑和 \(1\)\(j\) 的最短路徑構成(反證法即可得)。

\(dist\) 被初始化為:

\[dist = \begin{bmatrix} 0 & 5 & 10 & 10* & inf \\ inf & 0 & inf & 5 & 15 \\ inf & 30 & 0 & 20 & inf \\ 10 & 15* & 20* & 0 & 25 \\ 40 & 45* & 50* & inf & 0 \end{bmatrix} \]