分块算法详解
一. 啥是分块
分块,顾名思义,就是把一个东西分成多个块来进行维护操作,也被称为优雅的暴力。
二. 分块的操作
1. 分组
我们设数组长度为 N N N ,分成 K K K 块,每块长度 N K \dfrac{N}{K} KN 。对于一次区间操作,对区间內部的整块进行整体的操作,对区间边缘的零散块单独暴力处理。
所以,块数不能太少也不能太多。所以,一般来说,我们取得块长(块的长度)为 N \sqrt{N} N 。这样我们的时间复杂度即为带 N \sqrt N N 的。这是一种根号算法。
尽管比不上线段树以及树状数组,但是分块的可灵活性,但不用一层一层传数据的特性,让分块算法更加难以模板化。
分块代码:
int block=sqrt(n); //块长
for(int i=1;i<=n;i++)
{
belong[i]=(i-1)/block+1; //每一个位置的分组
}
2. 区间加法&单点查询
我们可以先将区间首位和末位的余块(不完全的块)暴力加上一个数。中间完整的块可以维护一个 tag \operatorname{tag} tag 数组,加上一个数的时候把 tag \operatorname{tag} tag 数组也加上这个数。最后输出的时候把原数加上 tag \operatorname{tag} tag。
代码:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
const int Maxn=5e4+10;
int belong[Maxn];
int Ar[Maxn];
int tag[Maxn];
int main()
{
int n;
scanf("%d",&n);
int block=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&Ar[i]);
}
for(int i=1;i<=n;i++)
{
belong[i]=(i-1)/block+1;
}
for(int i=1;i<=n;i++)
{
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0)
{
for(int i=l;i<=min(belong[l]*block,r);i++)
{
Ar[i]+=c;
}
if(belong[l]!=belong[r])
{
for(int i=(belong[r]-1)*block+1;i<=r;i++)
{
Ar[i]+=c;
}
}
for(int i=belong[l]+1;i<=belong[r]-1;i++)
{
tag[i]+=c;
}
}else{
printf("%d\n",Ar[r]+tag[belong[r]]);
}
}
return 0;
}
3.区间加法&询问区间内小于某个值 x 的元素个数
利用上一题的思想,可以先处理出 tag \operatorname{tag} tag 数组,然后维护一个 G \operatorname{G} G 的 vector \operatorname{vector} vector 数组,利用 lower bound \operatorname{lower}\operatorname{bound} lowerbound 函数求出。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
#define int long long
const int Maxn=5e4+10;
int belong[Maxn];
int n;
int block;
long long Ar[Maxn];
int tag[Maxn];
vector<int> G[Maxn];
void reset(int x)
{
G[x].resize(0);
for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
{
G[x].push_back(Ar[i]);