




第一部分 题目分析
1、看懂题目
(1)有一张无向带权图。
例如:
1 --2-- 2 --3-- 3 --4-- 4
\ |
5 1
\ |
5
(2)图中一共有
n 个点
m 条边
(3)现在不是只求一次最短路。
而是要求:
对于所有区间
[l,r]
都建立一个子图。
(4)例如
l=2
r=5
那么保留
2
3
4
5
其它点全部删掉。
(5)然后要求:
这个子图里面
所有点对最短路之和。
(6)最后
所有区间全部加起来。
2、举个例子
(1)假设
1
2
3
三点。
(2)边:
1-2=1
2-3=2
(3)那么区间有:
[1,1]
[1,2]
[1,3]
[2,2]
[2,3]
[3,3]
(4)每一个区间都要求
所有点之间最短路
是不是非常多?
(5)如果暴力:
O(n²)
每次跑 Floyd
O(n³)
总复杂度
O(n⁵)
显然不可能。需要更高效的做法。
第二部分 数据范围告诉我们什么?
1、题目里最关键的是:
n≤100
为什么?
100 非常小。
说明:
可以允许
100³
甚至
100⁴
但是
100⁵
就炸了。
2、所以我们要努力做到
O(n⁴)
第三部分 最容易想到的方法
1、每个区间:
跑一次 Floyd
区间数量:
n²
每次:
n³
总共:
n⁵
2、这是第一种思路。
直接淘汰。
第四部分 Floyd到底干了什么?
1、有的同学不会观察 Floyd。
它实际上就是:
一个一个加入中转点。
2、例如:
开始:
没有中转点
然后
加入
1
再加入
2
再加入
3
......
3、经典代码:
for(k)
for(i)
for(j)
dist=min(...)
其实就是:
当前允许经过
1~k
这些点。
4、所以:
Floyd最大的特点:
它是
动态增加中转点。
这就是本题突破口。
第五部分 本题也是不断增加点
1、观察区间。
(1)固定左端点:
l=3
(2)那么:
[3,3]
↓
[3,4]
↓
[3,5]
↓
[3,6]
是不是每次:
只增加一个点?
(3)增加的是:
r
例如:
[3,5]
↓
[3,6]
只增加
6
(4)是不是跟
Floyd加入中转点
特别像?
第六部分 如何利用?
1、对于固定
l
先复制一份最初距离:
g=f;
2、为什么?
因为:
f
永远保存原图。
而
g
不断更新。
所以:
每换一个
l
重新开始。
3、随后:
r=l
r=l+1
r=l+2
一直扩大。
每扩大一次:
新增一个点 r
那么:
只需要
让 r 做一次 Floyd 中转点
即可。
4、代码就是:
for(i)
for(j)
g[i][j]=
min(
g[i][j],
g[i][r]+g[r][j]
);
是不是跟 Floyd 一模一样?
5、区别只是:
普通 Floyd
k=1
2
3
...
n
这里
k=r
每次只增加一个。
第七部分 为什么这样就是对子图?
1、例如:
l=4
(1)第一次:
r=4
允许中转:
4
(2)第二次:
r=5
允许:
4
5
(3)第三次:
r=6
允许:
4
5
6
2、是不是:
永远都是:
[l,r]
里面的点?
3、没错。
所以:
求出来就是
子图
[l,r]
最短路
第八部分 为什么不用前面的点?
1、因为:
1
2
3
已经删掉了。
它们不存在。
自然不能作为中转点。
2、所以:
只加入
r
即可。
第九部分 为什么复制 g=f?
1、看看参考代码:
for(i)
for(j)
g[i][j]=f[i][j];
为什么?
2、假设:
l=1
更新结束以后:
g
已经包含很多信息。
下一次:
l=2
如果继续用:
g
里面还有:
点1
参与过中转。
那就错了。
2、所以:
必须:
重新复制。
第十部分 为什么只统计 i~r?
1、代码:
for(i=l;i<=r;i++)
for(j=i;j<=r;j++)
为什么?
2、因为:
题目只要求:
区间里面。
而不是:
整个图。
3、另外:
由于
dist(i,j)
=
dist(j,i)
无向图。
4、所以:
统计:
i≤j
即可。
避免重复。
第十一部分 参考程序
cpp
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 105;
const int mod = 1e9;
int n, m;
int f[N][N];
int g[N][N];
int ans;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
f[i][j] = mod;
f[i][i] = 0;
}
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
f[u][v] = min(f[u][v], w);
f[v][u] = min(f[v][u], w);
}
for (int l = 1; l <= n; l++) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] = f[i][j];
for (int r = l; r <= n; r++) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] = min(g[i][j], g[i][r] + g[r][j]);
for (int i = l; i <= r; i++)
for (int j = i; j <= r; j++)
ans = (ans + g[i][j]) % mod;
}
}
printf("%d\n", ans);
return 0;
}
1、初始化:
f[i][j]=INF;
f[i][i]=0;
表示:
没有边
无限大
自己到自己
0
2、然后:
读边:
f[u][v]=min(...)
f[v][u]=min(...)
为什么
min
因为:
有重边。
保留最短。
3、接着:
for(l=1;l<=n;l++)
固定左端点。
复制:
g=f;
然后:
for(r=l;r<=n;r++)
不断扩大区间。
新增一个点:
r
于是:
for(i)
for(j)
g[i][j]=
min(
g[i][j],
g[i][r]+g[r][j]
);
4、最后:
for(i=l;i<=r;i++)
for(j=i;j<=r;j++)
ans+=g[i][j];
统计答案。
5、整个算法时间复杂度:
外层:
l
O(n)
×
r
O(n)
×
Floyd一次更新
O(n²)
=
O(n⁴)
当 (n=100) 时,大约是 (10^8) 级别操作,在题目的限制下是可以接受的。
第十二部分 本题考察什么?
1、这题真正考察的不是 Floyd 模板,而是:
能否发现 Floyd 的本质是"逐步加入中转点",再把这种思想迁移到区间子图中。
2、很多同学都会背:
for(k)
for(i)
for(j)
3、真正优秀的同学会想到:
-
加入一个点,就做一次松弛;
-
区间右端点每次增加一个点,正好可以作为新的中转点;
-
固定左端点、逐步扩大右端点,就能把总复杂度从 O(n⁵) 优化到 O(n⁴)。
本节课总结
这一题最值得记住的不是代码,而是下面四句话:
✅ ① Floyd 的核心不是三层循环,而是"不断加入新的中转点"。
✅ ② 固定左端点,右端点每次增加一个点,就相当于新增一个中转点。
✅ ③ 每增加一个点,只需要执行一次 Floyd 的松弛更新,而不用重新计算全部最短路。
✅ ④ 学算法不要只记模板,要理解算法背后的思想,才能在新题目中灵活迁移。