最实用之数据结构思想——分块
前言
虽然分块是一种数据结构,但是我认为它更像一种思想:分部分处理序列。
虽然分块的时间复杂度一般比树状数组和线段树高,但是它能解决很多树状数组和线段树解决不了的事情。比如:树状数组和线段树在维护不满足区间可加、可减性的信息时显得吃力,代码实现也不简单直观。这时候,常常就要请出我们的分块大法啦。
它更通用、更容易实现,也更加直观。(这就是时间复杂度越高的算法越通用)它实际上是一种优雅的暴力。
简介:
分块,顾名思义,就是将一个序列分成几块,然后对于修改和询问有技巧地整合各块的信息。这里的技巧就是分块的核心思想:“大段维护,小段朴素”。
分块的划分也具有一定的策略,但始终遵循一个原则:“化学反应速率原理” 整个程序的时间复杂度取决于时间复杂度最高的一步,所以要做到合理地分块,预处理一部分信息并保存下来,用空间换取时间,达到时空平衡,才能达到降低时间复杂度的目的。
【模板】 线段树 1
这里还是一这道题为例,区间修改 + 区间询问。
我们先来想想如何分块才能最快。
假设将原序列分成
当且仅当
所以,当我们将原序列分成
比如,对于一个长度为
对于第
可以用如下代码记录每块的左右端点:
for(int i = 1; i <= t; i++) L[i] = (i - 1) * sqrt(n) + 1, R[i] = i * sqrt(n);
if(n > R[t]) t++, L[t] = R[t - 1] + 1, R[t] = n;
另外,预处理出区间和数组
区间修改
对于区间加指令
-
若
和 同时处于第 段内,就直接朴素修改,将 都加上 ,同时令 。 -
否则,设
处于第 段, 处于第 段。(1) 对于
,令 。(2) 对于开头、结尾不足一整段的两部分,按照情况
的方法朴素修改。
比如要在区间
区间查询
对于区间查询指令
-
若
和 同时处于第 段内,则 就是答案。 -
否则,设
处于第 段, 处于第 段,初始化 。(1) 对于
,令 ,其中 表示第 段的长度。(2) 对于开头、结尾不足一整段的两部分,按照情况
的方法朴素累加。
由于段数和段长都是
代码:
#include <iostream>
#include <cmath>
using namespace std;
const int N = 100010;
typedef long long ll;
int n, m, t;
ll a[N];
int L[N], R[N];
ll add[N], sum[N];
int pos[N];
void change(int l, int r, ll val) {
int p = pos[l], q = pos[r];
if(p == q) {
for(int i = l; i <= r; i++) a[i] += val;
sum[p] += (r - l + 1) * val;
}
else {
for(int i = l; i <= R[p]; i++) a[i] += val;
sum[p] += val * (R[p] - l + 1);
for(int i = L[q]; i <= r; i++) a[i] += val;
sum[q] += val * (r - L[q] + 1);
for(int i = p + 1; i <= q - 1; i++) add[i] += val;
}
}
ll query(int l, int r) {
int p = pos[l], q = pos[r];
ll res = 0;
if(p == q) {
for(int i = l; i <= r; i++) res += a[i];
res += (ll)add[p] * (r - l + 1);
}
else {
for(int i = l; i <= R[p]; i++) res += a[i];
res += (ll)add[p] * (R[p] - l + 1);
for(int i = L[q]; i <= r; i++) res += a[i];
res += (ll)add[q] * (r - L[q] + 1);
for(int i = p + 1; i <= q - 1; i++) res += (ll)add[i] * (R[i] - L[i] + 1) + sum[i];
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
t = sqrt(n);
for(int i = 1; i <= t; i++) L[i] = (i - 1) * sqrt(n) + 1, R[i] = i * sqrt(n);
if(n > R[t]) t++, L[t] = R[t - 1] + 1, R[t] = n;
for(int i = 1; i <= t; i++) {
for(int j = L[i]; j <= R[i]; j++) {
pos[j] = i;
sum[i] += a[j];
}
}
int op, x, y;
ll k;
while(m--) {
scanf("%d%d%d", &op, &x, &y);
if(op == 1) {
scanf("%lld", &k);
change(x, y, k);
}
else {
printf("%lld\n", query(x, y));
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效