分块:2025.2 练习记录
P3372 【模板】线段树 1
虽然这是道线段树题,但是数据范围只有 \(10^5\),拿来作为分块模板题也未尝不可。
计算出段长以后,将数列按照这个段长划分(最后一段可以不满),同时记录每个数所属段的编号。
int len; struct Block{ int l,r; LL dat,add; }bk[N]; int idx,bel[N]; ... len=sqrt(n); bk[++idx].l=1; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); if(i-bk[idx].l+1>len) { bk[idx].r=i-1; bk[++idx].l=i; } bel[i]=idx; bk[idx].dat+=a[i]; } bk[idx].r=n;
然后对于每一个段,记录一个 add
,表示该段被整体加上某个数的次数。
-
修改:对于整段,直接加在
add
上;对于两侧非整段部分,则直接在原数组a
上暴力累加。 -
查询:对于整段,加上该段记录的
a
之和,再加上该段add
值乘以段长;对于非整段,暴力累加a
,注意累加的每个a
都需要加一下该段的add
值。
这样,修改和查询的时间复杂度都是 \(O(\sqrt{n})\),总时间复杂度 \(O(n\sqrt{n})\),可过。
注意:分块时一般都要特判左右边界在同一段内的情况并进行特殊处理,不要妄想你的代码能自己处理好这个情况。
P4168 [Violet] 蒲公英
题目要求求区间众数。
将数列分块以后,设查询区间为 \([l,r]\),其中连续整块为 \([L,R]\),则区间被分为了 \([l,L-1][L,R][R+1,r]\) 三部分,进而可以分为在整块内的部分 \([L,R]\) 和不在整块内的部分 \([l,L-1]\cup[R+1,r]\),显然众数只有可能是整块部分的众数或者非整块部分出现的数。
对于整块部分,因为块数为 \(\sqrt{n}\),所以可以直接预处理所有第 \(i\) 块到第 \(j\) 块的众数,总数为 \(O(n)\) 级别,运用双指针等技巧可以轻松做到 \(O(n\sqrt{n})\) 预处理。
对于非整块部分,考虑如何快速求出某一个数在某段内出现的次数。这个应该是经典技巧,我在这篇题解里就用到了:记录每一个数出现的位置并排序,通过两次二分查找找到这些位置中在所查找区间内的一段。这一段的段长就是所求的出现次数。
总时间复杂度 \(O(n \sqrt{n} \log n)\) 级别,理论上来说是会部分超时的(分块:一生之痛)。但是如果设块长为 \(l\),那么时间复杂度拆开应该是 \(O(l \times m \log n + \frac{n^2}{l})\),《算法竞赛进阶指南》上说取块长 \(l=\sqrt{\frac{n}{\log n}}\) 最佳(实测这样可过),我实测下来大约取 \(l=\frac{\sqrt{n}}{10}\) 左右最快,
这个最优块长的计算我现在暂时不会,到时候学会了到这里补上。
P10590 磁力块
建立一个 BFS 模型:每次取队头,用它来找可达磁石;每找到一块新磁石就将它入队。
这道题有两个参数:距离和质量。如果将所有磁石按照距离排序,那么距离可达磁石就是一段以 \(1\) 为左端点的确定区间,然而这段区间内并不是所有石头质量都满足条件。如果按照质量排序,那么距离又不都满足条件了。
我们需要找一个方法兼顾这两个参数,而这个是可以用分块实现的:将所有磁石先按照距离排序再分块,这样就可以保证块与块之间的距离参数是递增的;再对每一个块内按照质量排序,这样就可以保证块内磁石的质量参数是递增的。
这样,我们就兼顾了两个参数:从头连续多个整段内的磁石都满足距离参数,这些段内无需再考虑距离;这些整段内从头连续区间的磁石都满足质量参数,可以愉快地使用每次砍左边一段区间的方式避免重复扫描。最后一个非整段暴力处理的时间复杂度也是可以接受的 \(O(\sqrt{n})\)。
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18755641
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步