如何优雅的暴力——分块
前言#
近期也是把hzwer的数列分块入门肝完了,感觉分块很玄学(
什么是分块#
分块的基本思想是,通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。
分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相应的时间复杂度。
分块是一种很灵活的思想,相较于树状数组和线段树,分块的优点是通用性更好,可以维护很多树状数组和线段树无法维护的信息。
当然,分块的缺点是渐进意义的复杂度,相较于线段树和树状数组不够好。
不过在大多数问题上,分块仍然是解决这些问题的一个不错选择。
分块的巧妙之处在于对大块通过预处理后能够实现快速对区间信息进行修改,零散块暴力,大块整体修改,使得分块虽然看起来是暴力,但是时间复杂度是
分块一般要维护的信息#
我个人的习惯是维护每个块的头和尾,每个点所在块的编号,以及每个块的标记。
关于块长
分块的预处理#
定义
int len = sqrt(n), num = n / len + (n % len ? 1 : 0);
for(int i = 1;i <= num;i ++)
t[i] = ed[i - 1] + 1, ed[i] = st[i] + len - 1;
for(int i = 1;i <= num;i ++)
for(int j = st[i];j <= ed[i];j ++)
pos[j] = i;
下面以LOJ的数列分块入门为例。
数列分块入门1#
题意:区间加,单点查询。
很裸的题目,有各种方法做,我这里选择分块。
考虑对每个块维护一个标记
举个栗子:
1 2 3 | 4 5 6 | 7 8 9 | 10
a : 0 0 0 | 0 0 0 | 0 0 0 | 0
pos: 1 1 1 | 2 2 2 | 3 3 3 | 4
tag: 0 | 0 | 0 | 0
当我们要将区间
1.先将左边零散块
1 2 3 | 4 5 6 | 7 8 9 | 10
a : 0 0 114514 | 0 0 0 | 0 0 0 | 0
pos: 1 1 1 | 2 2 2 | 3 3 3 | 4
tag: 0 | 0 | 0 | 0
2.将中间大块的
1 2 3 | 4 5 6 | 7 8 9 | 10
a : 0 0 114514 | 0 0 0 | 0 0 0 | 0
pos: 1 1 1 | 2 2 2 | 3 3 3 | 4
tag: 0 | 114514 | 0 | 0
3.将右边零散块
1 2 3 | 4 5 6 | 7 8 9 | 10
a : 0 0 114514 | 0 0 0 | 114514 0 0 | 0
pos: 1 1 1 | 2 2 2 | 3 3 3 | 4
tag: 0 | 114514 | 0 | 0
此时我们的修改就完成了,如果我们想查询
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
const int N = 5e5 + 5, INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int n;
int a[N];
int pos[N], block[N], st[N], ed[N], tag[N];
void change(int l, int r, int c)
{
if(pos[l] == pos[r])
{
for(int i = l;i <= r;i ++)
a[i] += c;
}
else
{
for(int i = l;i <= ed[pos[l]];i ++)
a[i] += c;
for(int i = pos[l] + 1;i <= pos[r] - 1;i ++)
tag[i] += c;
for(int i = st[pos[r]];i <= r;i ++)
a[i] += c;
}
}
int main() //主函数
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int i = 1;i <= n;i ++)
cin >> a[i];
int len = sqrt(n), num = n / len + (n % len ? 1 : 0);
for(int i = 1;i <= num;i ++)
st[i] = ed[i - 1] + 1, ed[i] = st[i] + len - 1;
ed[num] = n;
for(int i = 1;i <= num;i ++)
for(int j = st[i];j <= ed[i];j ++) pos[j] = i;
for(int i = 1;i <= n;i ++)
{
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) change(l, r, c);
else cout << a[r] + tag[pos[r]] << '\n';
}
return 0;
}
数列分块入门2#
咕咕咕
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】