介绍
最短路算法常与深度优先搜索(DFS)、动态规划(DP)、二分答案、拓扑排序等算法结合使用:
- 最短路与DFS结合:在一些图的路径问题中,当需要访问特定的多个结点,且数据范围较小时,可使用DFS。例如在一个有(n)个结点、(m)条无向边的图中,有(x)个结点必须访问,求最短路径。此时问题可转化为如何排列这(x)个结点,使得从起始结点依次经过其余结点所经过的路径最短。由于DFS时间复杂度较高,一般先用Dijkstra算法对这(x)个结点间的最短路做预处理,然后用DFS进行结点排列尝试,时间复杂度约为(O(ntimes ntimeslog(m)))。
- 最短路与DP结合:在涉及路径选择和最优值求解,且问题具有最优子结构性质时,会结合DP。比如在一个图中,从起点到终点的过程中,在不同的结点有不同的花费或收益,同时路径存在一定的限制条件,要求找到能获取最大收益或最小花费的路径。可以用DP来记录每个状态(到达某个结点的情况)下的最优值,用最短路算法来确定路径的可达性和距离信息,二者相互配合求解。
- 最短路与二分答案结合:当问题是求解使得某一条件满足的最优值(如路径中第(k)大的边最小)时,且该最优值存在二段性(即大于该值的情况和小于该值的情况有明显区分),可以使用二分答案的方法。通过二分枚举可能的最优值,然后利用最短路算法来判断在该值下是否满足题目条件(如查找该路径中有多少条边的权重大于枚举值),从而逐步缩小范围找到最优解。
- 最短路与拓扑排序结合:在处理有向无环图(DAG)且存在边权(可正可负)的最短路问题时,常结合拓扑排序。例如有多块区域通过道路(边权大于(0))和航线(边权可正可负,单向)相连,求最短路。由于存在负权边,不能直接用Dijkstra算法。此时先对区域间的道路用Dijkstra算法处理,对于航线,利用拓扑排序得到拓扑序,根据该顺序计算各个区域间的最短路,这样能有效处理负权边和有向图的情况。
新年好(dfs+最短路)
分析:
这道题可以结合深度优先搜索(DFS)和 SPFA(Shortest Path Faster Algorithm)算法来求解,以下是具体分析:
算法思路
图的存储:使用邻接表来存储图的结构,对于每个车站(节点),记录与其相连的车站以及通过公路所需的时间(边的权重)。
SPFA 求单源最短路径:从车站 1 出发,利用 SPFA 算法计算到车站 a, b, c, d, e 的最短路径。SPFA 算法基于队列实现,不断更新到各个节点的最短距离。初始时,将起点的距离设为 0,其他节点设为无穷大。每次从队列中取出一个节点,更新其相邻节点的距离,如果某个节点的距离被更新且该节点不在队列中,则将其加入队列。重复此过程,直到队列为空。
DFS 进行路径搜索:在得到从车站 1 到各个亲戚所在车站的最短距离后,使用 DFS 来枚举所有可能的拜访顺序。从车站 1 出发,依次选择一个未访问过的亲戚所在车站进行访问,记录路径长度,当访问完所有亲戚所在车站后,再返回车站 1,计算总路径长度。通过比较所有可能路径的总长度,找出最短的那一条。
伪代码:
2:通信线路(二分+最短路):
分析:
这道题可以通过二分答案和最短路算法来求解,以下是详细分析:
算法思路
1. 二分答案:由于要找的是完成升级的最少花费,而这个花费是路径上剩余电缆中最贵的那条的价格,所以可以对这个价格进行二分。价格的范围是从 (1) 到所有电缆升级花费中的最大值 (L_{max})。每次二分一个价格 (mid),判断是否存在一条从 (1) 号基站到 (N) 号基站的路径,在这条路径上不超过 (K) 条电缆的花费大于 (mid)。如果存在这样的路径,说明可以尝试更小的价格;如果不存在,则需要增大价格。
2. 判断路径是否存在(最短路算法):在二分过程中,对于每个假设的价格 (mid),需要判断是否存在满足条件的路径。这里可以使用最短路算法(如迪杰斯特拉算法或SPFA算法)进行改造。将花费大于 (mid) 的电缆看作权重为 (1)(表示这条电缆是需要额外付费考虑的),花费小于等于 (mid) 的电缆看作权重为 (0)。然后求从 (1) 号基站到 (N) 号基站的最短路,如果最短路的权重小于等于 (K),则说明存在满足条件的路径。
伪代码:
3-道路与航线(拓扑排序+最短路):
分析:
一种很巧妙的解题思路,利用连通块、拓扑排序和迪杰斯特拉算法结合来求解从发送中心城镇 (S) 到每个城镇的最便宜方案
算法思路
1. 划分连通块: - 仅考虑道路(因为道路是无向且可构成连通区域),使用深度优先搜索(DFS)或并查集算法来标记每个城镇所属的连通块。通过遍历所有道路,将相互连通的城镇划分到同一个连通块中,这样可以把整个图按道路连通性划分为若干个连通块。 - 记录每个连 通块包含的城镇节点。
2. 构建连通块之间的拓扑图: - 考虑航线,对于每条航线 ( (u, v) ) ,找到 (u) 和 (v) 所属的连通块 (block_u) 和 (block_v) 。如果 (block_u neq block_v) ,则在连通块之间构建一条有向边 ( (block_u, block_v) ) ,并记录这条边对应的航线信息(如花费等)。同时,记录每个连通块的入度(即指向该连通块的有向边数量)。 - 由于题目条件保证航线构成的图无环,所以这些连通块之间的有向边也构成一个有向无环图(DAG)。
3. 拓扑排序与迪杰斯特拉结合: - 把入度为 (0) 的连通块入队,开始拓扑排序。 - 对于每个连通块,先在其内部使用迪杰斯特拉算法,计算该连通块内从属于该连通块且是发送中心城镇 (S) 所在的节点(如果 (S) 在该连通块内)到其他节点的最短路径。 - 当处理一个连通块时,检查从该连通块出发的航线(即连通块之间的有向边),对于每条航线对应的目标连通块,如果目标连通块的入度减为 (0) ,则将其入队。同时,根据航线的花费,更新目标连通块内节点的最短距离(如果通过这条航线能使距离更短)。 - 重复上述步骤,直到队列为空,此时得到的就是从 (S) 到各个城镇的最短距离。
伪代码:
4最优贸易:
分析:
这种利用跑正反两次 SPFA 算法的思路是很巧妙的,下面详细分析其原理、实现步骤以及复杂度:
思路原理:
正向 SPFA:从 1 号城市出发,使用 SPFA 算法遍历所有能到达的城市。在遍历过程中,记录从 1 号城市到每个城市 k 路径上经过的所有城市中水晶球价格的最小值,存放在 dmin[k] 中。这样就能得到从起点 1 号城市到各个可达城市路径上的最低买入价格。
反向 SPFA:将图中的边方向全部反向(或者在实现时以相反的逻辑处理),从 n 号城市出发再次使用 SPFA 算法。这次记录从 n 号城市到每个城市 k 路径上经过的所有城市中水晶球价格的最大值,存放在 dmax[k] 中 。也就是得到了从各个城市到终点 n 号城市路径上的最高卖出价格。
计算结果:通过遍历所有城市 k,计算 dmax[k] - dmin[k],取其中的最大值就是在从 1 号城市出发到 n 号城市的过程中,进行最多一次买卖水晶球能赚取的最大旅费。如果所有的差值都小于等于 0,则表示赚不到差价。