在本文中,我们使用一种不同的动态规划公式来解决所有节点对的最短路算法。称为Flody-Warshall算法。其运行时间为 O ( V 3 ) O(V^3) O(V3)。
Flody-Warshall算法考虑的是某一条最短路上的中间节点。
设图 G G G的节点集合为 V = { 1 , 2 , … , n } V=\{1,2,\ldots,n\} V={1,2,…,n},令 V V V的一个子集 V k = { 1 , 2 , … , k } , k ≤ n V^k=\{1,2,\ldots,k\},k \leq n Vk={1,2,…,k},k≤n,设矩阵 w i j k w^k_{ij} wijk为所有满足以下下条件的路径中的最短路径:
那么全源最短路为 w n w^n wn,下面说明如何由 w k − 1 w^{k-1} wk−1推出 w k w^k wk。
则有动态规划方程:
d i j k = { w i j k = 0 min ( d i j k − 1 , d i k k − 1 + d k j k − 1 ) k ≥ 1 d^k_{ij} = \{\begin{matrix} w_{ij} & k = 0 \\ \min(d^{k-1}_{ij},d^{k-1}_{ik} +d^{k-1}_{kj}) & k \geq 1 \end{matrix} dijk={wijmin(dijk−1,dikk−1+dkjk−1)k=0k≥1
其中 k = 0 k = 0 k=0表示没有中间节点,那么就是初始图。
根据滚动数组的思想,我们并没有改变以后的状态,因此我们可以写出一下Flody算法的代码:
void flody(int n)
{
for (int k = 1; k <= n; k++) // 递推中间节点
for (int i = 1; i <= n; i++) // 初始节点
for (int j = 1; j <= n; j++) // 结束节点
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
P1119
灾后重建,理解Flody的模板题目。我们将按照修建的顺序将节点进行排序,那么得到的顺序就是Flody算法中中间节点 k k k更新的顺序。
另外Flody还能用来求传递闭包,传递闭包指在原图中若存在路径 i → j i \to j i→j,那么在传递闭包图中 i i i和 j j j相邻。
我们只需要替换最小算子和加法算子为逻辑算子即可。
void flody(int n)
{
for (int k = 1; k <= n; k++) // 递推中间节点
for (int i = 1; i <= n; i++) // 初始节点
for (int j = 1; j <= n; j++) // 结束节点
d[i][j] = d[i][j] || d[i][k] && d[k][j];
}
若在某种DAG的DP中,存在路径 i → k → j i \to k \to j i→k→j比边 ( i , j ) (i,j) (i,j)更优,那么边 ( i , j ) (i,j) (i,j)是不必要的。
那么也就是说,在传递闭包中所有的传递路径都是不优的,我们在DP中根本不需要遍历这些边。
我们用下述算法优化DP:
经过证明可知,一个完全DAG图经过上述优化之后边数 O ( n 2 ) O(n^2) O(n2)最后优化为 O ( n ) O(n) O(n),即若有 n n n个节点的DAG图,那么最终最多剩下 n − 1 n-1 n−1条边,即 1 1 1到 n n n的一个链。
2021CCPC for Female C
这个题就是经典的传递闭包优化DAG上的DP。
int own[40];
int w[40];
int G[40][40];
int C[40][40];
map<ll, int> dp[40];
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> own[i];
for (int i = 1; i <= n; i++)
cin >> w[i];
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
G[u][v] = C[u][v] = 1;
}
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
C[i][j] = C[i][j] | C[i][k] & C[k][j];
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (G[i][j] && C[i][k] && C[k][j])
G[i][j] = 0;
dp[1][ST(0, own[1])] = w[own[1]];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
if (!G[i][j])
continue;
for (auto [stat, val] : dp[i])
{
if (!GT(stat, own[j]))
{
dp[j][ST(stat, own[j])] = max(dp[j][ST(stat, own[j])], val + w[own[j]]);
}
else
{
dp[j][stat] = max(dp[j][stat], val);
}
}
}
for (int i = 1; i <= n; i++)
{
int ans = 0;
for (auto [stat, val] : dp[i])
{
ans = max(ans, val);
}
cout << ans << endl;
}
}