图论总结

应 Zikme 要求,不放代码。好像更抽象了

茅坑顿开。

0. 前言

下文中:

  • \(u \to v\) 表示从 \(u\)\(v\) 的单向边,\(w_{u,v}\)\(u \to v\) 的边权。
  • \(P(u,v)\) 表示 \(u\)\(v\) 的最短路径。
  • \(s\) 为源点,\(t\) 为终点。
  • \(d_u\)\(s\)\(u\) 的最短路长度。(算法执行中)
  • \(D_u\)\(s\)\(u\) 的最终最短路长度。

越简单的算法,证明越难想。——Zikme

1. dijkstra

1. 流程简述

算法核心是将图分为两个点集 \(S,\ T\)。其中 \(S\) 为已确定最短路点集,\(T\) 为未确定最短路点集。

算法流程:

重复以下操作,直至 \(T=\emptyset\)

  1. \(T\) 中选取最短路长度最小的点 \(v\)。令 \(u\)\(S\) 中存在 \(u \to v\) 的点中最短路长度最小的点。

  2. 进行松弛操作:\(d_v=\min\{d_v,d_u+w_{u,v}\}\)

  3. \(v\)\(T\) 移至 \(S\)\(D_v=d_v\)


2. 算法证明

显然,最短路有一个性质:对于一个最短路径,其前缀仍然是最短路。

故一定存在上述 \(u\) 点。

所以,只要证明加入 \(T\) 集合时 \(v\) 点的最短路已被确定(即 \(v\) 加入 \(S\)\(D_v=d_v\),dijkstra 算法得证。

考虑用反证法证明。

注:该证明只适用于边权非负的情况。这便是为什么 dijkstra 不能跑负权图。

\(u\) 为移动至 \(S\) 集合时 \(D_u<d_u\) 的点。

当前最短路径与实际最短路径至少存在一个点不相同。

\(v_1,v_2\) 为当前最短路径与实际最短路径第一个不相同的点。

\(s\)\(u\) 的当前最短路径为 \(p_1:s \to \cdots \to v_1 \to \cdots \to u\)\(s\)\(u\) 的实际最短路径为 \(p_2:s \to \cdots \to v_2 \to \cdots \to u\)

可将其简化为 \(p_1:s \to v_1 \to u,\ p_2:s\to v_2\to u\)

显然,\(v_2\) 还未被更新到的时候,\(p_1\) 才能成为最短路径。

所以有 \(v_2\in T\)

又因为 \(v_2\in p_2\) 且在 \(u\) 前,所以一定有 \(d_{v_2}<d_u\)。(注:只有在边权非负的情况下才成立。

与 “\(d_u\) 为所有 \(p\in T\)\(d_p\) 最小” 矛盾。故不存在 \(v_2\) 使得 \(p_1\) 不为最短路径。

dijkstra 算法得证。

3. 时间复杂度

暴力时间复杂度:

  • 查找 \(T\) 集合中 \(d_u\) 的最小值,时间复杂度 \(O(n)\)
  • 松弛一条边,时间复杂度 \(O(1)\)

对于图中 \(n\) 个点进行上述操作,总时间复杂度 \(O(n\times(n+1))=O(n^2)\)

优化:

由于要查找 \(T\) 集合中 \(d_u\) 的最小值,可以使用支持修改的最值数据结构。

可选的有:

  • 堆:每松弛一条边 \(u\to v\), 将 \(v\) 插入堆或修改 \(v\) 的权值,共 \(m\) 次;同时需取 \(n\) 次堆顶。时间复杂度 \(O((n+m)\log n)=O(m\log n)\)

  • 优先队列:由于优先队列不支持直接修改,故队列中有 \(m\) 个元素。总时间复杂度 \(O(m\log m)\)

哪个正经人线段树

但是当数据为稠密图 (\(m\approx n^2\)) 时,上述优化时间复杂度均退化为 \(O(n^2\log n)\) 级别。还不如暴力

2. Bellman-Ford / SPFA 证明

1. 流程简述

其本质为松弛操作:\(d_u=\min\{d_u,d_v+w_{u,v}\}\)

对图上的每一条边尝试松弛,共松弛 \(m\) 次。

意义显然。

其核心为:对于 \(P(u,v)\), 经过 \(n\) 次松弛即可保证正确性。

2. 算法证明

证明如下:

一次松弛至少可以将最短路的长度加 \(1\)

由于最短路径不可能重复经过一条边(无负权环),最坏情况(最短路为链状)需要将 \(n\) 个点全部遍历。故 \(|P(u,v)|<n\)

所以至多经过 \(n\) 次松弛操作即可保证正确性。得证。

综上所述,总时间复杂度为 \(O(n\times m)\)

3. 算法优化

显然,大多数的松弛操作都是无效的。

而只有上一次被松弛过的节点(可能在最短路径中)所连接的边才可能会有有效的松弛操作。

考虑用队列维护。反正最坏情况下复杂度也为 \(O(n\times m)\)

复杂度证明:

看不懂。不写了。反正非负权用 nm (指时间复杂度 \(O(n\times m)\)) 的 spfa。

至于怎么让这玩意死透,@changwenxuan 已经进行了详尽的研究。

干点正事。

3. Floyd 证明

1. 流程简述

\(g_{u,v}\)\(u\to v\) 的最短路。

有转移方程 \(g_{u,v}=\min\{g_{u,v},g_{u,k}+g_{k,v}\}\)

2. 算法证明

其原始方程为 \(g_{k,u,v}=\min\{g_{k-1,u,v},g_{k-1,u,k}+g_{k-1,k,v}\}\),表示只经过前 \(k\) 个点,\(u\to v\) 的最短路。

时间复杂度 \(O(n^3)\),空间复杂度 \(O(n^2)\)

试证明第一维可以直接省略。

显然有 \(f_{k-1,u,v}=f_{k,u,v}\)如果没有,图上就有负环。最短路什么的也无所谓了。

故可以将第一维直接省略。

4. Common Tricks

1. 最短路树

由于最短路的前缀也一定是最短路,所以对于单源最短路,其构成一棵有根叶向树。

主要用处是统计方案数和 dp。

2. 虚点

对于某些具有相同性质的点,可以另建一个点并将其与具有相同性质的点连边。

相当于将具有相同性质的点缩为一个点集,大幅减少边数及点数。

3. 分层图

运用 dp 思想,将不同决策导致状态不同的点装进不同的图内。

虽然一般来说这样的决策只能有一个。不然边数爆炸。

在大多数情况下等价于直接在图上进行多维 dp。

虽然我觉得建图要好看一些。

例题大赏

题目不按任何顺序排序。

[SDOI2010] 大陆争霸

第一眼看上去好像是最短路加了个强制拓扑。

也就是说当结界还没被破坏的时候,已经到达的机器人只能干等着。

在 dijkstra 中,机器人所在的点可以更新最短路。但拓扑图上该点的入度不为 \(0\),即结界产生器没有被全部破坏时,不能入队。

当炸掉一个结界产生器的时候,可以将其在拓扑图上连接的所有点的入度减一。

而当拓扑图上的一个点入度为 \(0\),即结界产生器全部被破坏的时候,便可以入队。

不用考虑当前是否有机器人在该点等待。即使没有,由于题目保证有解,所以若该点在 \(1\to n\) 的最短路上一定可达。之后也会有更优决策来替代它。

爆改 dijkstra。需要对算法流程有一定的理解。

CF1648E

题目传送门

在一带权无向图上,每次只能连续经过两条边,且代价为两条边边权之和的平方。求单源最短路。

数据范围 \(1\leq n\leq10^5,\ 1\leq m \leq 2\times10^5,\ 1\leq w\leq50\)

暴力建图的话总边数为 \(m^2\),会爆炸。

考虑一个路径 \(u\to x\to v\)\(w_1=w_{u,x},\ w_2=w_{x,v}\)

由于一次移动至少要经过两条边,所以上述代价和等价于将边权全部移到 \(x\to v\) 上。

这样的话 \(u\to x\) 就没那么重要了。在 \(w_2\) 一定的情况下共有 \(w=50\) 种可能的权值。

可以考虑以 \(w_1\) 的值构造分层图。

\((w,u)\) 为入边边权为 \(w\) 且入点为 \(u\)点集。但是由于其并不影响最终结果,可以将其作为一个点。

则对于点对 \((u,x,v)\),可以将 \(u\)\((w_1,u)\) 连一条边权为 \(0\) 的边;将 \((w_1,u)\)\(v\) 连一条边权为 \((w_1+w_2)^2\) 的边。

显然 \(u\to v\) 的代价等价于原图。

显然,对于一个点 \(u\)(假定其为路径的起始点),分层后其最大出度为 \(w=50\)

总边数从 \(m^2\) 骤降到了 \(mw\)

总时间复杂度为 \(O(m\log m)\) 左右。

分层图的各种妙妙玩法。

没思路时可以考虑转移边权或点权。

例如缩点,点权转边权等。

[HAOI2010] 道路

题目传送门

不管怎么说,双倍经验


题意很简洁了。

对于每个源点 \(s\),先跑一遍 dijkstra。显然,若满足 \(dis_v=dis_u+w_{u,v}\),则 \(e(u,v)\) 一定在最短路上。

显然在 \(w_{u,v}>0\) 时,不存在 \(u,v\) 使得 \(dis_u=dis_v+w_{u,v} \wedge dis_v=dis_u+w_{u,v}\)

因此,若将最短路径上的点从原图中取出,加入集合 \(V\),其构成的图一定为 DAG \(G(V,E)\)

可以考虑将其进行拓扑排序。

其实是可以在 dijkstra 中排序的。dijkstra 的遍历顺序就是拓扑序。但是如果你硬要再写一遍也没人拦你

然后捏?

图上 dp。

\(G(V,E)\) 的源点(集)为 \(s'\),汇点(集)为 \(t'\)

对于任意 \(E_i=e(u,v)\),设 \(out_i\)\(s'\to u\) 的方案数,\(in_i\)\(v\to t'\) 的方案数。

根据乘法原理,得 \(ans_i=in_i\times out_i\)

\(out_i\) 好求。转移方程为 \(out_u=out_u+out_v[dis_u+w_{u,v}=dis_v]\ \ e(u,v)\in E\)

事实上 \(in_i\)\(out_i\) 的求法是一样的。

\(v\to t'\) 这部分建个反图 \(G'(V',E')\) 就可以达到同样的效果。

转移方程为 \(in_u=in_u+in_v[dis_v+w_{u,v}=dis_u]\ \ e(u,v)\in E'\)

在 DAG 上算方案数有两种基本策略:

  • 按照拓扑序 dp。
  • 对于每个点 / 边分类讨论后,运用组合数学求方案数。

[NOIP2017] 逛公园

看到 \(k=50\),考虑按路径长度为一维度 dp。

\(f_{u,k}\) 为 从 \(1\)\(u\) 路径长度为 \(d_u+k\) 的总方案数。

需要以倒拓扑序的顺序进行转移。

对于图上的每条边 \(e(u,v)\),有转移方程:

\(f_{u,k}=\sum\limits_{e(u,v)}f_{v,k-(d_u + w - d_v)} \ (d_u+w-d_v<k)\)

其中 \(d_u+w_{u,v}-d_v\) 为较 \(d_v\) 经过 \(e(u,v)\) 多走的路程。

考虑按路径长度进行记忆化搜索求解。

具体实现可以建反图后搜索。

注:在搜索过程中如果遇见 \(0\) 环,则存在无穷种路径,输出 \(-1\)。可以通过维护每个点在当前路径长度被搜索到的次数判环。

若题目对某一条件存在限制,可以考虑将条件列入 dp 方程中求解。

[GZOI / GXOI2019] 旅行者

如果已知分组方案,对两组分别建立虚拟原点后跑最短路即可。

主要问题是如何分组。

随机化算法 shuffle 战神

设答案源点为 \(u\),汇点为 \(v\)

\(K\) 分为大小相同的两组 \(K_1,K_2\)\(u\in K_1\wedge v\in K_2\) 的概率为 \(\frac 1 4\)

故尝试 \(T\) 次全错的概率为 \(1-(\frac 3 4)^T\)

尝试 \(20\) 次左右就行了。正确率为 \(99.68\%\)

upd: NKOJ过不了。

二进制分组

按照 \(K_i\) 二进制上的每一位分组。

正确性证明:

显然源点与汇点是不同的点。故其至少有一个二进制位不同。将其按每一位的二进制分组,至少有一次源汇点会被分在不同组。

upd: NKOJ也过不了。

upd2: 没判 -1 导致的。

时间复杂度 \(O(m\log^2n)\)

果国的奇妙旅行

题目传送门

下文中令 \(d_u\) 为点 \(u\) 的度数。

如题,求最优策略下的期望购票数。

期望:若有连续随机变量 \(x\),其期望 \(\mu_x=\sum\limits_{x\in D}x\times p_x\)

其中 \(D\) 为定义域,\(p_x\)\(x\) 出现的概率。

由于结果确定,通常期望 dp 采用倒推的方式。

\(f_u\)\(u\)\(n\) 的期望购票数。

将决策分开考虑。对于每个在点 \(u\) 抽到了去点 \(v\) 的票,有两种决策:移至 \(v\) 点或留在原地。

这样的状态设计可以让实际决策形成一个 DAG。

显然,若有 \(f_v\geq f_u\),最优决策为留在原地,期望为 \(\frac {f_u}{d_u}+1\)

反之则可以移至点 \(v\),期望为 \(\frac {f_v} {d_u}+1\)

故有转移方程

\[\forall e(u,v):f_u=1+\frac1{d_u}(\sum\limits_{f_u\leq f_v}f_u+\sum\limits_{f_u>f_v}f_v) \]

写起来挺好看的。但是在 \(f_u\) 的递推式中出现了自己。那就不好玩了。

不能用正常顺序求解。

注意到一个性质:\(\forall e(u,v):\min f_u\) 一定是由 \(\min f_v\) 转移过来的。

感觉和 dijkstra 有点像。考虑在 dijkstra 的过程中维护 \(f_u\)

可以考虑把 \(f_u\) 替换成其他的可维护变量。

然后发现好像 \(f_u>f_v\) 的数量是可以维护的。顺便把 \(\sum\limits_{f_u>f_v}f_v\) 的值维护了。

每次选出当前已知的最小 \(f_v\)。对于每条边 \(e(u,v)\), 令 \(l_u\) 为满足 \(f_u>f_v\) 的数量,\(s_u\) 为满足 \(f_u>f_v\)\(f_v\) 总和。

将上述方程化简得:

\[f_ud_u=d_u+(d_u-l_u)f_u+s_u \]

解得:

\[\begin{matrix}f_u=\frac{s_u+d_u}{l_u}&(f_u>f_v)\end{matrix} \]

注:可以将 \(f_u\) 初始化为 \(\infin\)。~~如果后面有解的话,这反正也不是最优解。和 \(dis_u=\infin\) 一个意思。

优雅。这样就将 \(f_u\) 这个无法维护的量转为了 \(d_u\)\(l_u\)\(s_u\) 三个可以维护的量。

图上期望 dp 的思路为:

  • 确定最优策略
  • 倒序 dp
  • 若方程存在自环,可以将方程拆开后根据可维护变量进行转移。

[NKOJ8736] 指针分析

我也不知道这玩意为什么要放到最短路专题里。

将字母压位后 模 拟 即 可。

posted @   CQWDX  阅读(92)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示