蓝桥杯真题‘k倍区间’与‘路径’:2 种同余定理与 Floyd 算法实战对比
蓝桥杯真题‘k倍区间’与‘路径’同余定理与Floyd算法的深度解析与实战对比1. 引言蓝桥杯竞赛中的数学与算法思维在蓝桥杯这类算法竞赛中数学思维与算法设计的结合往往是解题的关键。两道典型题目——2017年的k倍区间和2020年的路径问题恰好展示了这种结合的两种不同形态。前者需要巧妙运用数论中的同余定理优化暴力解法后者则考验选手对图论中最短路径算法的灵活应用。这两道题目虽然表面看似毫无关联但深入分析会发现它们都体现了算法竞赛的核心思想通过数学洞察减少计算复杂度。k倍区间通过同余性质将O(n²)的暴力枚举优化为O(n)的精妙解法路径问题则利用Floyd算法将看似复杂的多源最短路径转化为三重循环的优雅实现。本文将带领读者深入剖析这两道题目的解题思路对比不同解法的性能差异并总结出适用于同类问题的通用解题模板。无论是正在备赛蓝桥杯的选手还是希望提升算法能力的编程爱好者都能从中获得启发。2. k倍区间问题从暴力枚举到同余优化2.1 问题描述与暴力解法题目要求给定一个长度为N的数列和一个整数K统计有多少个连续子序列的和是K的倍数。例如当N5K2数列为[1,2,3,4,5]时满足条件的子序列有6个。最直观的解法是暴力枚举所有可能的子序列def brute_force(nums, k): count 0 n len(nums) for i in range(n): current_sum 0 for j in range(i, n): current_sum nums[j] if current_sum % k 0: count 1 return count这种解法的时间复杂度为O(n²)当n达到1e5量级时题目中N≤1e5显然会超时。2.2 前缀和优化及其局限性引入前缀和数组S其中S[i]表示前i个元素的和可以将区间和计算优化为O(1)def prefix_sum(nums, k): n len(nums) prefix [0]*(n1) for i in range(n): prefix[i1] prefix[i] nums[i] count 0 for i in range(n): for j in range(i1, n1): if (prefix[j] - prefix[i]) % k 0: count 1 return count尽管优化了区间和的计算但双重循环的结构使得时间复杂度仍为O(n²)无法通过大规模测试用例。2.3 同余定理的巧妙应用同余定理指出如果两个数a和b满足(a-b) mod k 0那么a mod k b mod k。将这个性质反向应用我们可以统计前缀和对k取模后的余数相同余数的前缀和两两相减得到的区间必然满足k倍区间的条件。优化后的算法实现def optimized_k_sum(nums, k): from collections import defaultdict prefix 0 count 0 remainder_count defaultdict(int) remainder_count[0] 1 # 初始状态前缀和为0 for num in nums: prefix num rem prefix % k count remainder_count[rem] remainder_count[rem] 1 return count该算法仅需一次遍历时间复杂度降为O(n)空间复杂度O(k)完美解决了大规模数据的问题。关键点初始化时remainder_count[0]1是因为前缀和为0本身就是一个有效的起点相当于从数组开头开始的子序列。3. 路径问题图论中的最短路径挑战3.1 问题描述与图建模题目要求计算从结点1到结点2021的最短路径其中任意两个结点i和j|i-j|≤21之间的边权值为i和j的最小公倍数。首先需要将问题建模为图论问题结点1到2021的整数边任意两个相差不超过21的结点之间有一条边边权为它们的最小公倍数3.2 最小公倍数与最大公约数的计算计算边权需要先实现最大公约数(GCD)和最小公倍数(LCM)def gcd(a, b): return a if b 0 else gcd(b, a % b) def lcm(a, b): return a * b // gcd(a, b)这两个函数的实现基于欧几里得算法时间复杂度为O(log min(a,b))。3.3 Floyd算法的实现与优化Floyd算法通过三重循环逐步更新所有结点对之间的最短路径def floyd_shortest_path(n): INF float(inf) dist [[INF]*(n1) for _ in range(n1)] # 初始化边权 for i in range(1, n1): for j in range(i, min(i21, n1)): if i j: dist[i][j] 0 else: dist[i][j] dist[j][i] lcm(i, j) # Floyd核心算法 for k in range(1, n1): for i in range(1, n1): for j in range(1, n1): if dist[i][j] dist[i][k] dist[k][j]: dist[i][j] dist[i][k] dist[k][j] return dist[1][n]对于n2021Floyd算法的时间复杂度为O(n³)虽然理论复杂度较高但在本题限制下n2021仍可接受。4. 算法对比与性能分析4.1 时间复杂度对比题目暴力解法优化解法优化技术k倍区间O(n²)O(n)同余定理哈希表路径O(n³)O(n²) (Dijkstra)堆优化/优先队列4.2 空间复杂度对比题目暴力解法优化解法k倍区间O(1)O(k)路径O(n²)O(n²)4.3 适用场景总结同余优化适用于需要统计满足特定模数条件的区间或子序列问题Floyd算法适合结点数较少(≤500)的多源最短路径问题编码简单但效率较低Dijkstra替代方案对于单源最短路径且边数较多的情况使用堆优化的Dijkstra算法更高效5. 同类型问题的通用解题模板5.1 同余类问题模板def modulo_subarray_count(nums, k): from collections import defaultdict prefix 0 count 0 mod_count defaultdict(int) mod_count[0] 1 # 初始化 for num in nums: prefix num current_mod prefix % k count mod_count[current_mod] mod_count[current_mod] 1 return count5.2 最短路径问题模板def shortest_path(n, edges): INF float(inf) dist [[INF]*(n1) for _ in range(n1)] # 初始化邻接矩阵 for u, v, w in edges: dist[u][v] w dist[v][u] w # 无向图 # Floyd-Warshall算法 for k in range(1, n1): for i in range(1, n1): for j in range(1, n1): if dist[i][j] dist[i][k] dist[k][j]: dist[i][j] dist[i][k] dist[k][j] return dist6. 实战技巧与注意事项6.1 同余问题的常见变种负数处理在取模运算中不同语言对负数的处理不同需要统一转换为正余数rem prefix % k if rem 0: rem k多模数条件可能需要统计满足多个模数条件的子序列最大长度子序列记录最早出现每个余数的位置计算最大长度6.2 最短路径算法的选择策略算法适用场景时间复杂度空间复杂度Floyd多源最短路径小图(n≤500)O(n³)O(n²)Dijkstra单源最短路径无负权边O(EVlogV)O(EV)Bellman-Ford单源最短路径可处理负权边O(VE)O(V)SPFA稀疏图中的单源最短路径平均O(E)最坏O(VE)O(V)6.3 蓝桥杯中的典型陷阱整数溢出在计算LCM时中间结果可能超出int范围需要使用long long边界条件如n0或k1时的特殊处理输入规模根据题目给出的数据范围选择合适的算法浮点精度涉及浮点数比较时需要设置误差容忍度7. 扩展思考与进阶挑战7.1 k倍区间问题的数学证明同余定理在此问题中的应用可以形式化证明设前缀和S[j] ≡ r (mod k)S[i] ≡ r (mod k)其中ij 则S[j] - S[i] ≡ 0 (mod k) 即子序列A[i1..j]的和是k的倍数7.2 Floyd算法的动态规划本质Floyd算法实际上是动态规划的一种应用定义dp[k][i][j]表示经过前k个中间结点时i到j的最短路径 状态转移方程 dp[k][i][j] min(dp[k-1][i][j], dp[k-1][i][k] dp[k-1][k][j])7.3 相关题目推荐同余类LeetCode 974和可被K整除的子数组蓝桥杯2018倍数问题最短路径蓝桥杯2019迷宫LeetCode 743网络延迟时间综合应用蓝桥杯2021最小权值LeetCode 1786从第一个节点出发到最后一个节点的受限路径数