最短路之和

最短路之和

给定一个 $n$ 个点的加权有向图,点的编号为 $1 \sim n$。

图中任意两点之间都有两条方向相反的边连接两点。

从点 $i$ 到点 $j$ 的边的长度为 $a_{ij}$。

给定一个 $1 \sim n$ 的排列 $x_1,x_2, \ldots ,x_n$。

你需要对该图依次进行 $n$ 个操作。

其中,第 $i$ 个操作是将点 $x_i$ 以及该点的所有入边和出边从图中移除。

在执行每一个操作之前,你还需要进行如下计算:

  • 对于每个当前剩余点对 $(u,v)$,计算从点 $u$ 到点 $v$ 的最短路长度。
  • 将这些最短路长度全部相加得到最短路之和。

注意:

  • 剩余点对 $(u,v)$:两个还未被移除的点 $u,v$($u \ne v$)组成的点对。
  • $(u,v)$ 和 $(v,u)$ 是两个不同点对,需分别计算最短路长度并计入最短路之和。
  • $n$ 个操作是按顺序依次执行的,前面的操作会对后面的计算产生影响。

请你输出执行每个操作前计算得到的最短路之和。

输入格式

第一行包含整数 $n$。

接下来 $n$ 行,每行包含 $n$ 个整数,其中第 $i$ 行第 $j$ 个数为 $a_{ij}$。

最后一行包含 $n$ 个整数 $x_1,x_2, \ldots ,x_n$。

输出格式

共一行,输出 $n$ 个整数,其中第 $i$ 个整数表示执行第 $i$ 个操作之前,计算得到的最短路之和。

数据范围

前 $4$ 个测试点满足 $1 \leq n \leq 4$。
所有测试点满足 $1 \leq n \leq 500$,$1 \leq a_{ij} \leq {10}^{5}$,$a_{ii}=0$,$1 \leq x_i \leq n$,保证 $x_1 \sim x_n$ 是一个 $1 \sim n$ 的排列。

输入样例1:

1
0
1

输出样例1:

0

输入样例2:

2
0 5
4 0
1 2

输出样例2:

9 0

输入样例3:

4
0 3 1 1
6 0 400 1
2 4 0 1
1 1 1 0
4 1 2 3

输出样例3:

17 23 404 0

 

解题思路

  比赛的时候猜对了做法,这题本质就是考对Floyd算法原理的理解。

  因为这题要求任意两点间的最短路径,所以首先容易想到用Floyd。然后就是每次从点集中删除一个点,求点集中剩余点的任意两点间的最短距离的总和。删点的话不好做,那么试试倒过来做行不行。就是一开始点集为空,然后每次往点集中加入一个点(以及对应的边),再求点集中任意两点最短路。如果了解Floyd算法的原理的话那么就会知道,当第一层循环枚举完$k$,此时求得的是任意两点间只经过节点编号$1 \sim k$的最短距离。因此每次我们当我们枚举到$k$,那么就可以求出任意两点间只经过前$k$个点的最短距离,而前$k$个点就是我们往点集中加的前$k$个点。

  下面补个Floyd的推导过程,其实本质就是个dp。

  定义状态$f(k,i,j)$表示所有从$i$出发,最终走到$j$,且中间只经过节点编号不超过$k$的所有路径,属性就是路径长度的最小值。根据$i \to j$的路径中(不包含起点$i$和终点$j$)是否包含节点$k$来划分集合。

  1. 如果$i \to j$的路径不包含节点$k$,那么意味着当前$i \to j$的所有路径中只含有节点$1 \sim k-1$,对应的集合就是$f(k-1,i,j)$。
  2. 如果如果$i \to j$的路径包含节点$k$,那么有$i \to \cdots \to k \to \cdots \to j$,其中$i \to \cdots \to k$和$k \to \cdots \to j$的路径中只含有节点$1 \sim k-1$,分别对应的集合为$f(k-1,i,k)$和$f(k-1,k,j)$。

  对这两种情况取最小值,那么就有状态转移方程$$f(k,i,j) = \min \left\{ {f(k-1,i,j),\ f(k-1,i,k)+f(k-1,k,j)} \right\}$$

  然后就是证明把状态定义的第一维$k$去掉得到的$f(i,j)$与原本的状态是等价的。

  当$j = k$时,原本有$f(k,i,k) = \min \left\{ {f(k-1,i,k),\ f(k-1,i,k)+f(k-1,k,k)} \right\}$,由于$f(k-1,k,k) = 0$(初始化的时候有$f(0,k,k) = 0$,即$k$到本身的最小距离为$0$),因此得到$f(k,i,k) = \min \left\{ {f(k-1,i,k),\ f(k-1,i,k)} \right\} = f(k-1,i,k)$。因此对于$j=k$的情况去掉第一维不会影响结果,因为状态$f(i,k)$存的是上一层的$f(i,k)$的结果。

  同理当$i = k$时,原本有$f(k,k,j) = \min \left\{ {f(k-1,k,j),\ f(k-1,k,k)+f(k-1,k,j)} \right\}$,由于$f(k-1,k,k) = 0$,因此得到$f(k,k,j) = \min \left\{ {f(k-1,k,j),\ f(k-1,k,j)} \right\} = f(k-1,k,j)$。因此对于$i=k$的情况去掉第一维不会影响结果,因为状态$f(k,j)$存的是上一层的$f(k,j)$的结果。

  对于一般的$f(k,i,j)$,有$$\begin{align*} f(k,i,j) &= \min \left\{ {f(k-1,i,j),\ f(k-1,i,k)+f(k-1,k,j)} \right\} \\  &= \min \left\{ {f(k-1,i,j),\ f(k,i,k)+f(k,k,j)} \right\} \end{align*}$$

  因此去掉第一维可以发现$f(i,j) = \min \left\{ {f(i,j),\ f(i,k)+f(k,j)} \right\}$,与之前的状态转移方程是等价的。

  因此状态转移方程就可以写成$$f(i,j) = \min \left\{ {f(i,j),\ f(i,k)+f(k,j)} \right\}$$

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 510;
 7 
 8 LL g[N][N];
 9 int p[N];
10 LL ans[N];
11 
12 int main() {
13     int n;
14     scanf("%d", &n);
15     for (int i = 1; i <= n; i++) {
16         for (int j = 1; j <= n; j++) {
17             scanf("%lld", &g[i][j]);
18         }
19     }
20     for (int i = 1; i <= n; i++) {
21         scanf("%d", p + i);
22     }
23     for (int k = n; k; k--) {   // 倒序插入点,求只经过p[k]~p[n]的最短路径
24         for (int i = 1; i <= n; i++) {
25             for (int j = 1; j <= n; j++) {
26                 g[i][j] = min(g[i][j], g[i][p[k]] + g[p[k]][j]);
27             }
28         }
29         for (int i = n; i >= k; i--) {  // 只统计已加入的点的结果
30             for (int j = n; j >= k; j--) {
31                 ans[k] += g[p[i]][p[j]];
32             }
33         }
34     }
35     for (int i = 1; i <= n; i++) {
36         printf("%lld ", ans[i]);
37     }
38     
39     return 0;
40 }

 

参考资料

  AcWing 4872. 最短路之和(AcWing杯 - 周赛):https://www.acwing.com/video/4653/

  3.2 floyd算法及其扩展应用:https://www.acwing.com/file_system/file/content/whole/index/content/147349/

posted @ 2023-03-12 17:53  onlyblues  阅读(35)  评论(0编辑  收藏  举报
Web Analytics