发布时间:2026/7/19 0:40:19
分块思想在算法中的工程化:平方分割与莫队算法的实现要诀 分块思想在算法中的工程化平方分割与莫队算法的实现要诀一、区间查询问题线段树不是唯一答案线段树是处理区间查询的经典数据结构单次查询 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)) 的复杂度下解决了区间查询问题。平方分割是最基础的分块应用莫队算法则把分块引入查询重排领域大幅减少离线批量查询中指针移动的总次数。分块的魅力不在于复杂度和实现有多精致而在于思路的直白——把大的拆成小的整块的巧算零散的死算。这种简单但有效的套路在实际工程中比精巧但脆弱的数据结构更值得信赖。

相关新闻

2026/7/19 0:40:19

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

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

2026/7/19 0:40:19

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

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

2026/7/19 0:35:19

【AI问数】大模型选型与微调:AI问数的LLM落地实战

4 大选型维度 多模型 路由策略 99.97% 可用性 Spider 权威Benchmark AI问数对LLM的选型要求:中文理解强、SQL生成能力突出(Spider/Bird跑分)、上下文窗口足够(16K)、支持私有化部署。鲲溟KM AI采用多模型路由:主模型专用模型备用模型三层配置。 一…

2026/7/19 2:56:24

Python入门指南:从环境搭建到基础语法

1. Python基础入门:从零开始的第一天作为一名有十年Python开发经验的程序员,我经常被问到"如何开始学习Python"。今天我就带大家从最基础的环境搭建开始,完成Python入门的第一个里程碑。无论你是完全没有编程基础的小白&#xff0c…

2026/7/19 2:56:24

ngx_output_chain_add_copy

1 定义 ngx_output_chain_add_copy 函数 定义在 src/core/ngx_output_chain.cstatic ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,ngx_chain_t *in) {ngx_chain_t *cl, **ll; #if (NGX_SENDFILE_LIMIT)ngx_buf_t *b, *buf; #endifll chai…

2026/7/19 2:56:24

Linux平台驱动注册机制:platform_driver_register与probe对比

1. Linux平台驱动注册机制概述在Linux设备驱动开发中,平台设备(platform device)和平台驱动(platform driver)是用于描述片上系统(SoC)外围设备和其驱动程序的抽象模型。这种机制特别适用于那些不通过标准总线(如PCI、USB)连接的设备,比如集成在SoC内部的…

2026/7/19 2:56:24

Strut 2.0框架开发实战:从环境搭建到Demo部署

1. 项目概述"strut 2.0的一个小demo"这个标题看似简单,却蕴含了丰富的技术内涵。作为一名长期从事前端开发的工程师,我理解这里的"strut"很可能指的是某个前端框架或工具库的2.0版本,而"demo"则是展示其核心功…

2026/7/19 2:56:24

双链笔记系统设计与实践:高效知识管理方案

1. 学习笔记系统设计与实践作为一名长期与知识打交道的从业者,我深知高效的学习笔记系统对个人成长的重要性。今天要分享的这套"study note1"系统,是我经过多年迭代形成的知识管理方案,它完美融合了数字工具的高效性与传统笔记的深…

2026/7/19 2:51:24

if let 解决了“条件解包“的问题,但还有一种更常见的模式:函数开头验证输入,如果不合法就立刻返回。Swift 发明了 guard let 来应对这个场景,G# 原样采纳。 // G#:guar

if let 解决了"条件解包"的问题,但还有一种更常见的模式:函数开头验证输入,如果不合法就立刻返回。Swift 发明了 guard let 来应对这个场景,G# 原样采纳。 // G#:guard let —— “守卫"住必要条件 fun…

2026/7/19 0:00:15

Unity与Python本地通信:基于Flask的跨语言数据交换实战

1. 项目概述:为什么我们需要一个本地通信服务器?在游戏开发、数字孪生、仿真训练等众多领域,Unity作为强大的实时3D内容创作平台,其核心逻辑通常由C#驱动。然而,当我们需要进行复杂的数据分析、机器学习推理、科学计算…

2026/7/19 0:00:15

Unity与Python本地通信:基于Flask的跨语言数据交换实战

1. 项目概述:为什么我们需要一个本地通信服务器?在游戏开发、数字孪生、仿真训练等众多领域,Unity作为强大的实时3D内容创作平台,其核心逻辑通常由C#驱动。然而,当我们需要进行复杂的数据分析、机器学习推理、科学计算…

2026/7/18 16:50:29

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…