你真的懂 Floyd 吗

  • Floyd 求最短路,时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)
for (int k = 1;k <= n;++k)
	for (int i = 1;i <= n;++i)
		for (int j = 1;j <= n;++j) dis[i][j] = min (dis[i][j],dis[i][k] + dis[k][j]);
  • 【Q1】该算法的正确性?

算法基于动态规划。设 f k , i , j f_{k,i,j} fk,i,j 表示只能借助于 1 , 2 , ⋯   , k 1,2,\cdots,k 1,2,,k 点时, i → j i \to j ij 的最短路径。那么根据是否经过 k k k 点,可以列出状态转移方程:

f k , i , j = min ⁡ ( f k − 1 , i , j , f k − 1 , i , k + f k − 1 , k , j ) f_{k,i,j} = \min (f_{k - 1,i,j},f_{k - 1,i,k} + f_{k - 1,k,j}) fk,i,j=min(fk1,i,j,fk1,i,k+fk1,k,j)

很好理解,后者相当于以 k k k 作为中转站,即 i → k → j i \to k \to j ikj 的转移。那么当处理询问 u , v u,v u,v 时, f n , u , v f_{n,u,v} fn,u,v 即为所求。

可以发现 f k , i , j f_{k,i,j} fk,i,j 只对 f k − 1 , i ′ , j ′ f_{k - 1,i',j'} fk1,i,j 产生依赖,不难想到可以滚动数组优化。那么,Floyd 最终直接删去那一维,这样做不会出错吗?

现在我们去掉第一维,只要保证在转移时方程右侧的 f i , j , f i , k , f k , j f_{i,j},f_{i,k},f_{k,j} fi,j,fi,k,fk,j 都是未被覆盖过的即可。当最外层为第 k k k 次循环时,显然 f i , j f_{i,j} fi,j 还未被覆盖。对于 f i , k , f k , j f_{i,k},f_{k,j} fi,k,fk,j,按照之前的三维转移方程,可以得到 f k , i , k = min ⁡ ( f k − 1 , i , k , f k − 1 , i , k + f k − 1 , k , k ) = f k − 1 , i , k f_{k,i,k} = \min (f_{k - 1,i,k},f_{k - 1,i,k} + f_{k - 1,k,k}) = f_{k - 1,i,k} fk,i,k=min(fk1,i,k,fk1,i,k+fk1,k,k)=fk1,i,k f k , j f_{k,j} fk,j 同理。因此我们证明了空间优化的该算法的正确性。

与此同时,从动态规划的角度出发,“为什么 k k k 循环需要放在最外层?”也就很好理解了。

【Q2】若每次修改一条边的权值,有必要重新完整的去做一遍 Floyd 算法吗?

答案是否定的,只需要 O ( n 2 ) O(n^2) O(n2) 即可实现局部更新。设 ( u , v ) (u,v) (u,v) 的权值更新为 w ( u , v ) w(u,v) w(u,v)(无向图),则可得到转移方程:

f i , j = min ⁡ ( f i , j , min ⁡ ( f i , u + w ( u , v ) + f v , j , f i , v + w ( v , u ) + f u , j ) ) f_{i,j} = \min (f_{i,j},\min (f_{i,u} + w(u,v) + f_{v,j},f_{i,v} + w(v,u) + f_{u,j})) fi,j=min(fi,j,min(fi,u+w(u,v)+fv,j,fi,v+w(v,u)+fu,j))

例题:

Another Exercise on Graphs (easy version)
Another Exercise on Graphs (hard version)

解析:

根据边权排序,然后答案显然就是某一条边的值。当枚举到第 i i i 条边的时候,比其小的边均置为 0 0 0,否则为 1 1 1。若 i → j i \to j ij 的最短路是 w i w_i wi,那么第 i + 1 i + 1 i+1 大的便是这条边。

进一步的, E2 \texttt{E2} E2 m m m 很大,可是我们会发现有效的更新只有 n − 1 n - 1 n1 次,因为之后 ( i , j ) (i,j) (i,j) 的最短路已经为 0 0 0,不会再发生变化,并查集维护即可。

【Q3】变式 1 1 1 【模板】传递闭包

简单来说,如果原关系图上有 i i i j j j 的路径,则其传递闭包的关系图上就应有从 i i i j j j 的边,均用 0 / 1 0/1 0/1 表示。因此枚举中转点 k k k,若 ( i , k ) (i,k) (i,k) ( k , j ) (k,j) (k,j) 均有连边,则可将 ( i , j ) (i,j) (i,j) 连边。写成表达式,就是:

f i , j = max ⁡ ( f i , j , min ⁡ ( f i , k , f k , j ) ) f_{i,j} = \max (f_{i,j},\min (f_{i,k},f_{k,j})) fi,j=max(fi,j,min(fi,k,fk,j))

【Q4】变式 2 2 2 [USACO07NOV] Cow Relays G 本质同 Walk

A A A 中是经过 x x x 条边的最短路, B B B 中是经过 y y y 条边的最短路。通过以下转移后,可以发现 C C C 中是经过 x + y x + y x+y 条边的最短路:

C i , j = min ⁡ ( C i , j , A i , k + B k , j ) C_{i,j} = \min ({C_{i,j}},A_{i,k} + B_{k,j}) Ci,j=min(Ci,j,Ai,k+Bk,j)

这样我们的转移就变得简单,重载后直接通过矩阵快速幂即可解决问题。

进一步的变式 [SCOI2009] 迷路衡量距离。拆点与 bitset 的优化,时间复杂度 O ( n 3 64 log ⁡ k ) O(\frac{n^3}{64} \log k) O(64n3logk)。具体来说,对于一个点,拆成 v 1 ∼ v 10 v_1 \sim v_{10} v1v10,然后 u → v u \to v uv 一条边为 w w w 的边,然后 v i → v i − 1 v_i \to v_{i - 1} vivi1 进行连边,于是可以变为 u → v w u \to v_w uvw

明白了!你只需要编译两个可执行文件: - `test_server` - `test_multiclient` 每个可执行文件由一组指定的 `.c` 文件组成,不需要编译目录中其他多余的 `.c` 文件。 --- ### ✅ 简洁 Makefile 示例 下面是一个 **只编译你指定的两个可执行文件** 的 Makefile: ```makefile CC = gcc CFLAGS = -Wall -Wextra -g LDFLAGS = -pthread -lprotobuf-c # test_server 组成文件 TEST_SERVER_SRCS = test_server.c distribution_route.pb-c.c upload_subnet_entry.pb-c.c graph_operate.c floyd_spf.c TEST_SERVER_OBJS = $(TEST_SERVER_SRCS:.c=.o) # test_multiclient 组成文件 TEST_CLIENT_SRCS = test3_multiclient.c distribution_route.pb-c.c upload_subnet_entry.pb-c.c graph_operate.c floyd_spf.c routing_table_operate.c TEST_CLIENT_OBJS = $(TEST_CLIENT_SRCS:.c=.o) # 默认目标 all: test_server test_multiclient # 编译规则 test_server: $(TEST_SERVER_OBJS) $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) test_multiclient: $(TEST_CLIENT_OBJS) $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) # 模式规则:编译 .c 到 .o %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ # 清理 clean: rm -f *.o test_server test_multiclient .PHONY: all clean ``` --- ### ✅ 使用说明 - `make`:编译两个可执行文件 `test_server` 和 `test_multiclient` - `make clean`:删除所有 `.o` 文件和可执行文件 --- ### 📌 说明 - Makefile 中只包含你指定的源文件,不会自动包含目录中其他 `.c` 文件。 - 支持增量编译(即只重新编译修改过的文件)。 - 如果你希望将某些 `.pb-c.c` 文件复用,可以进一步优化为静态库或中间目标。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值