分块思想在算法中的工程化:平方分割与莫队算法的实现要诀
分块思想在算法中的工程化平方分割与莫队算法的实现要诀一、区间查询问题线段树不是唯一答案线段树是处理区间查询的经典数据结构单次查询 O(log n)功能强大。但它的实现代码量不小——建树、更新、查询三个递归函数一套下来至少几十行。而且不是所有场景都适合用线段树。如果数据是静态的不会在线更新或者查询的模式比较固定有没有比线段树更简单、更暴力但仍然够快的办法答案就是分块。分块思想的核心很简单把大规模数据按固定大小切成若干块预处理每块的聚合信息。查询时完整的块直接拿预计算的值零散的部分暴力计算。这样一次查询的复杂度在 O(sqrt(n)) 左右——比 O(log n) 慢一点但实现复杂度远低于线段树。在很多场景中O(sqrt(n)) 已经足够快。flowchart TD A[原始数组] -- B[按固定大小 sqrt#40n#41 分块] B -- C[块 0: 索引 0~B-1] B -- D[块 1: 索引 B~2B-1] B -- E[块 k: 索引 kB~n-1] C -- F[预计算块 0 的聚合值] D -- G[预计算块 1 的聚合值] E -- H[预计算块 k 的聚合值] I[区间查询 L, R] -- J{分析查询区间} J -- K[左边零散部分: 暴力遍历] J -- L[中间完整块: 直接用预计算值] J -- M[右边零散部分: 暴力遍历] K -- N[合并结果] L -- N M -- N二、平方分割最朴素的块最实用的效果平方分割是分块思想最基础的实现。假设数组长度为 n每块大小取 sqrt(n) 左右。为什么要取 sqrt(n)因为一次查询最多涉及 sqrt(n) 个完整块每块 O(1) 拿聚合值和两端各 sqrt(n) 个零散元素暴力计算——总复杂度 O(sqrt(n))。如果块大小取得太大零散部分变多如果块大小取得太小完整块变多。sqrt(n) 恰好在这两者之间取得最优平衡。以区间求和为例。每块预存块内元素的和。查询区间 [L, R] 时对于 L 所在的块中从 L 到块尾的部分逐个累加。对于 L 和 R 之间的完整块取出预计算的和O(1) 搞定。对于 R 所在的块中从块头到 R 的部分逐个累加。更新操作同样简单修改元素值后更新该元素所在块的聚合值。整个实现不超过 40 行代码。三、莫队算法把分块用在查询重排上分块不仅能优化数据结构还能优化查询的执行顺序。莫队算法就是这样一个技巧当有大量离线区间查询不需要在线返回、允许批量处理后一并输出时通过调整查询的执行顺序让指针移动的总次数从 O(mn) 降到 O(nsqrt(m))。莫队算法的核心操作是把所有查询按左端点所在块号排序同块内的按右端点排序。排序后维护两个指针 L 和 R表示当前处理到的区间。处理下一个查询时通过移动 L 和 R 来覆盖目标区间。因为排序后的查询顺序让指针的移动距离大大缩短总体复杂度从平方降到了 n*sqrt(m)。/** * 莫队算法离线区间查询优化 * * 适用条件 * 1. 查询是离线的不需要立刻回答 * 2. add/remove 操作是 O(1) 的可逆操作 * 3. 查询之间相互独立 * * 复杂度O(n * sqrt(m))n 是数组长度m 是查询数量 */ public class MoAlgorithm { // 块大小n / sqrt(m)使总复杂度最优 private int blockSize; /** * 处理所有区间查询 * * param arr 原始数组 * param queries 所有查询 [l, r, index] * return 按原始顺序排列的查询结果 */ public int[] processQueries(int[] arr, int[][] queries) { int n arr.length; int m queries.length; // 块大小理论最优值为 n / sqrt(m) // 这里用 n / Math.sqrt(m) 是为了让指针移动次数最少 blockSize (int) (n / Math.sqrt(m)); if (blockSize 0) blockSize 1; // 第一步将查询按莫队排序规则排列 // 规则1左端点所在块号小的排前面 // 规则2同块内按右端点排序奇数块升序、偶数块降序进一步优化 Query[] qs new Query[m]; for (int i 0; i m; i) { qs[i] new Query(queries[i][0], queries[i][1], i); } Arrays.sort(qs, (a, b) - { int blockA a.l / blockSize; int blockB b.l / blockSize; if (blockA ! blockB) { return blockA - blockB; } // 奇偶块排序优化减少 R 指针的无谓移动 return (blockA % 2 0) ? a.r - b.r : b.r - a.r; }); // 第二步从头开始处理所有查询维护 [curL, curR] 区间 int curL 0, curR -1; int curSum 0; // 当前区间的聚合值以求和为例 int[] results new int[m]; for (Query q : qs) { // 扩展右边界包含更多元素 while (curR q.r) { curR; curSum arr[curR]; // add 操作O(1) } // 收缩右边界排除多余元素 while (curR q.r) { curSum - arr[curR]; // remove 操作O(1) curR--; } // 扩展左边界包含更多左侧元素左边界左移 while (curL q.l) { curL--; curSum arr[curL]; // add 操作O(1) } // 收缩左边界排除多余左侧元素左边界右移 while (curL q.l) { curSum - arr[curL]; // remove 操作O(1) curL; } // 四个 while 的顺序很重要 // 先扩后缩可以避免指针交叉导致的数组越界 results[q.index] curSum; } return results; } /** * 查询内部类 */ private static class Query { int l, r; // 查询区间 [l, r] int index; // 原始顺序用于恢复输出顺序 Query(int l, int r, int index) { this.l l; this.r r; this.index index; } } }四个 while 循环的顺序不是随意排列的。基本原则是先扩展再收缩避免指针位置非法。如果先收缩左边界while (curL q.l) curL在 curR 还没扩展到位的情况下curL 可能越过 curR造成 sum 减去了不该减的元素。正确的顺序是先扩展右边界和左边界再收缩右边界和左边界。四、分块方法的适用场景与限制分块思想的优势在于实现简单和适用面广。不像线段树需要预定义区间操作的类型和、最大值、最小值分块对区间操作的类型几乎没有限制——只要块的聚合能快速更新就行。而且分块天然支持单点更新这一点比前缀和要灵活。但它的劣势也同样明确在线查询时 O(sqrt(n)) 比 O(log n) 慢n 达到 10^6 时差距就明显了。莫队只适用于离线查询需要拿到全部查询后才能重排。分块的常数因子不小块大小、排序策略都会影响实际效率需要针对具体数据做调优。分块和线段树不是替代关系而是互补关系。复杂动态区间操作如区间乘加混合更新用线段树简单静态或偶有更新的数据用分块。选择的依据是实现的复杂度 VS 查询的复杂度哪个在你的场景里权重更高。五、总结分块思想用暴力 预处理的组合在 O(sqrt(n)) 的复杂度下解决了区间查询问题。平方分割是最基础的分块应用莫队算法则把分块引入查询重排领域大幅减少离线批量查询中指针移动的总次数。分块的魅力不在于复杂度和实现有多精致而在于思路的直白——把大的拆成小的整块的巧算零散的死算。这种简单但有效的套路在实际工程中比精巧但脆弱的数据结构更值得信赖。

相关新闻

AI 工具的用户反馈闭环:从隐性信号到模型优化

AI 工具的用户反馈闭环:从隐性信号到模型优化

AI 工具的用户反馈闭环:从隐性信号到模型优化 一、用户反馈不只是「好评」和「差评」 独立产品的用户反馈,传统形式是评分(1-5星)或评论。对于 AI 工具,这些显式反馈(用户主动给出的评价)有价值,但其覆盖率通常不到 1%——绝大多数用户不会主动评价。如果只依赖显…

2026/11/12 3:41:11 阅读更多 →
AI 任务的优先级调度:不同用户、不同任务的资源分配

AI 任务的优先级调度:不同用户、不同任务的资源分配

AI 任务的优先级调度:不同用户、不同任务的资源分配 一、当 AI 调用开始排队 产品在成长期,AI 调用量不再是「即来即处理」。在高并发时刻(如工作时间、产品推广期),AI API 的请求可能会出现排队——用户的请求发出了,但需要等待前面的请求处理完才能轮到。 如果所有…

2026/11/12 13:35:15 阅读更多 →
migration-tools迁移报告:如何生成和分析迁移结果的完整指南

migration-tools迁移报告:如何生成和分析迁移结果的完整指南

migration-tools迁移报告:如何生成和分析迁移结果的完整指南 【免费下载链接】migration-tools tools for migrating systems 项目地址: https://gitcode.com/openeuler/migration-tools 前往项目官网免费下载:https://ar.openeuler.org/ar/ mig…

2026/11/10 3:24:25 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/12 5:00:00 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/12 5:13:20 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/12 5:13:20 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/12 18:22:26 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/12 18:22:25 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/12 5:13:20 阅读更多 →

日新闻

周新闻

月新闻