分块与根号算法入门
update:
- 2024.4.13:完工,修改与整理
- 2025.3.4: 修 markdown,移除一些大分块至分块进阶,加入树分块
0. 根号算法
一些无法以
wcnm 联合省选 2025 D1T2。
1. 整除分块
这一部分较为简单。
1.1 概念与解法
整除分块是要求形如
暴力是
我们考虑把
证明,分类讨论:
- 若
,只有 个数, 显然只有 个。 - 若
,因为 ,也只有 个。
所以我们这样计算复杂度为
若有
我们令
得
这样只需累加
1.2 拓展
1.2.1 多维整除分块
即求
只需令
使所有
1.2.2 常用式子
1.3 例题
拆分问题则答案为
求前
则每个约数
答案就是
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll l,r;
ll ask(ll n){
ll ans = 0;
for(ll l = 1,r;l <= n;l = r + 1){
r = min(n,n / (n / l));
ans += (r - l + 1) * (l + r) / 2 * (n / l);
}
return ans;
}
int main(){
scanf("%lld%lld",&l,&r);
printf("%lld\n",ask(r)-ask(l-1));
return 0;
}
直接求即可。
求
则原式可写为
先考虑减号前的式子。
都可直接解决。减号后。
相当于二维整除分块。复杂度
注意
不是质数,需要别的方法求 与 的逆元。
2. 分块
2.1 算法简介
分块是一种思想,实质是将通过将一个序列分成几块,对于块内 预处理,或者说 整体修改 与 暴力重构。
对于一个序列,将每块假设有
当然,每道题的操作不同,整块 与 散块 的复杂度也会不同,
分块的优点是容易思考,比较暴力以及可以维护一些 复杂的信息。
2.2 其他分块
2.2.1 值域分块
类似于值域线段树,当值域较小时可用分块来维护值域,值域分块可以用来 平衡复杂度。
在单点修改,区间查询中。
值域分块可以
也可以
这样在某些题中可以取得更优的复杂度,详见莫队例 III。
2.2.2 块状链表
可插入的序列,设块长为
根号重构: 当一个块内添加新元素后达到了
这样最多分裂
2.2.3 树分块
对于一棵树,我们设一个阈值为
一个简单的方法是随机撒点,期望下是正确的。
严谨的,我们每次选择深度最深的非关键点,考虑其
这样对于一个链询问,我们可以分成四个长度
2.3 经典问题
区间修改,区间查询。
对于修改,散块 暴力改,整块 打标记。
对于询问,散块 暴力加,整块 直接加和,注意加上标记。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+10,M = 330;
const ll inf = 1e17;
int n,m;
ll a[N],sum[M],la[M];
int L[N],R[N],bl[N],B,t;
void prework(){
B = sqrt(n);t = (n-1)/B+1;
for(int i = 1;i <= t;i++)L[i] = (i-1)*B+1,R[i] = min(i*B,n);
for(int i = 1;i <= n;i++)bl[i] = (i-1)/B+1,sum[bl[i]] += a[i];
}
void modify(int l,int r,ll x){
int p = bl[l],q = bl[r];
if(p == q){
for(int i = l;i <= r;i++)a[i] += x,sum[p] += x;
return;
}
for(int i = l;i <= R[p];i++)a[i] += x,sum[p] += x;
for(int i = L[q];i <= r;i++)a[i] += x,sum[q] += x;
for(int i = p+1;i <= q-1;i++)la[i] += x;
}//区间修改
ll ask(int l,int r){
int p = bl[l],q = bl[r];
ll ans = 0;
if(p == q){
for(int i = l;i <= r;i++)ans += a[i] + la[p];
return ans;
}
for(int i = l;i <= R[p];i++)ans += a[i] + la[p];
for(int i = L[q];i <= r;i++)ans += a[i] + la[q];
for(int i = p+1;i <= q-1;i++)ans += la[i] * (R[i] - L[i] + 1) + sum[i];
return ans;
}//区间查询
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)scanf("%lld",&a[i]);
prework();
for(int i = 1;i <= m;i++){
int op,l,r;ll x;
scanf("%d%d%d",&op,&l,&r);
if(op == 1){
scanf("%lld",&x);
modify(l,r,x);
}
else printf("%lld\n",ask(l,r));
}
return 0;
}
2.4 例题
II P2801 教主的魔法
区间加,区间询问
的数的个数。
发现
对于修改,整块 中,打上标记即可,复杂度
对于询问,整块 中,块内是重构后排好序的,二分查找即可,复杂度
总复杂度
虽然分块不优,但是可以做个示例。
我们分块后,维护每个块内的 哈希表,询问对于 散块 暴力判断,整块 则询问哈希表,修改为哈希表复杂度。
若用 map
,复杂度
若平衡复杂度取 gp_hash_table
,仅需跑 600ms。
可以直观的感受平衡复杂度的思想。
区间查询众数,输出最小众数,强制在线。
考虑预处理,设
都可
考虑询问:
- 对于整块,直接提出
数组内众数即可。 - 对于散块,在桶内暴力统计,暴力比较 整块与散块 中个数和。
时间复杂度
但是仅仅这样还是不 毒瘤,lxl 卡了卡内存,变成了
IV P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III
只能要
发现是
这样空间复杂度降到了
这样就行了吗?不行!
本题必须严格根号复杂度才可过,继续考虑优化。
在询问中设当前整块的答案为
时间复杂度为严格
V P6177 Count on a tree II/【模板】树分块
树分块,选定阈值
然后考虑求答案,首先可以离散化,bitset
,我们预处理任意在一条到根的链上的 一对关键点 之间的 bitset
数组,复杂度
对于一个询问
我们暴力跳到一个关键点,记录 bitset
,然后向上接着跳关键点,最后再跳到 lca,复杂度
总复杂度
3. 莫队
莫队————优雅的暴力
3.1 算法简介
莫队是一种 离线 算法,资瓷修改。
主要思想:对询问分块(相当于对
证明:因为在一块内
- 奇偶性排序:当
在奇数块内 以降序排序,否则内 以升序排序,会使 呈类似折线形,可有效减少常数。
3.2 带修莫队
因为是离线,莫队一般不资瓷修改,但我们可以暴力增加一个 时间维,同
询问变成了三维,排序也要多以个关键字,类似普通莫队,我们以
我们分成了
这样复杂度是
证明:设
所在块不变, 是单增的,复杂度为 ,总 。- 对于
,在块内移动最多 次,总 。 - 对于
, 所在块不变,复杂度为 , 所在块变,最坏移动 次,复杂度为 。 - 总复杂度
,大约取到 时最小,为 。
3.3 回滚莫队
当维护的信息在区间增加与区间减小其中之一无法实现的情况下(如区间最大值),此时我们就需要 回滚莫队。
虽然不能够缩短或伸长,但增加或撤销操作也都是可以实现的。
具体操作(以不能缩短为例):
- 首先我们需要对序列 分块,然后以
所在块为第一关键字排序,以 为第二关键字排序。 - 如果
在同一块内,则暴力查询(类似分块)。 - 如果
不在同一块,设 所在块为 ,则将 伸缩为 ,将 伸缩为 。 点在块内只增加,而 有可能会是乱序的,则我们需要在完成该次询问后撤销回去,使 重回 ,形象的:可以再加一个变量 代替 进行该询问的增加,而 本身不变,即完成了 "撤销"。
类似的,若信息不能增加,则可以使
3.4 树上莫队
莫队只能在序列里使用,我们考虑把树的路径转化为序列。
欧拉序:在
我们称
则
- 若
是 的祖先,则是 到 。 - 若
互不为祖先,则是 到 再加上 。
这样可以把询问也改到序列上。
则我们只需要在转化后的序列跑普通莫队即可,当然带修也可以。
注意长度为
3.5 莫队二次离线
咕咕咕
3.6 例题
I P1494 [国家集训队] 小 Z 的袜子 [cogs 1775]
给定序列,求在
显然总概率为
只需按
在区间伸缩时,必须先 伸长 再 缩短。
II P1903 [国家集训队] 数颜色 / 维护队列 [cogs 1901]
给定序列,求
数据伸缩是简单的。考虑修改,维护时间轴
排序时以
III P4396 [AHOI2013] 作业[cogs 1822]
区间
可以离线,考虑莫队,先排序询问。
然后考虑指针伸缩,可以想到维护一个值域
可以考虑平衡复杂度,利用值域分块的
分析复杂度:
- 莫队
, 时最优,为 。 - 值域分块
。
总复杂度
IV Machine Learning
注意该题是求每个数出现次数的
我们考虑如何求
需要两个桶分别记录数组
套用上题,维护
复杂度
V 歴史の研究
求
可以发现,该信息增加是容易的,但是删除时却很困难(不知道次大值),需要用到 回滚莫队。
首先我们把序列分块,然后根据
考虑询问,若
VI COT2 - Count on a tree II
只是把区间操作改到了树上路径。
利用上述方法,将树上路径改为序列区间,将区间询问改为序列上区间(加上
区间伸缩与例 II 相同。
然后在序列上跑普通莫队即可。
VII P4074 [WC2013] 糖果公园
树上带修。
首先考虑将树上路径改为序列区间,拍成欧拉序,求一下
考虑区间伸缩,若增加一个点
然后可以直接在序列上跑 带修莫队 即可。
参考文章:
「分块」数列分块入门1 – 9 by hzwer
分块相关杂谈
『回滚莫队及其简单运用』(强烈推荐)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!