分块
定义
是一种分治思想,通常指的是序列分块。对于区间修改和区间询问总是分成左端不完整的块、中间完整的块、右端不完整的块三个部分处理
与线段树的区别
使用范围更广泛,但时间复杂度更高。
修改操作
维护一个区间标记 tag_itagi,表示第 ii 个块的修改值。
分情况讨论:
- 区间 [L,R] 在同一块中,暴力修改 aa 数组以及原块的信息 sumsum 数组。
- 区间 [L,R] 不在同一块中,分成三部分: ①. 左端不完整的块和右端不完整的块,参照情况 1 暴力。 ②. 中间完整的块,tag_i+=valtagi+=val
带修改询问
情况1:询问区间 [L,R] 在一个块中,暴力枚举 a_iai 和 tagtag 数组。
情况2:区间 [L,R] 不在同一块中,分为三个部分:
- 左端和右端不完整的块参照情况 1。
- 中间完整的块要处理 sum_isumi 和 tag_itagi。
模板代码
- 线段树1分块版
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int a[N];
int L[N], R[N], pos[N], n, m, t, tag[N], sum[N];
void update(int lt, int rt, int val)
{
int x=pos[lt], y=pos[rt];
if(x==y)
{
for(int i=lt;i<=rt;i++)
{
a[i]+=val;
sum[x]+=val;
}
}
else
{
for(int i=x+1;i<=y-1;i++)
{
tag[i]+=val;
}
for(int i=lt;i<=R[x];i++)
{
a[i]+=val, sum[x]+=val;
}
for(int i=L[y];i<=rt;i++)
{
a[i]+=val;
sum[y]+=val;
}
}
return ;
}
int query(int lt, int rt)
{
int x=pos[lt], y=pos[rt];
int ans=0;
if(x==y)
{
for(int i=lt;i<=rt;i++)
{
ans+=a[i];
}
}
else
{
for(int i=x+1;i<=y-1;i++)
{
ans+=sum[i]+tag[i]*(R[i]-L[i]+1);
}
for(int i=lt;i<=R[x];i++)
{
ans+=a[i]+tag[x];
}
for(int i=L[y];i<=rt;i++)
{
ans+=a[i]+tag[y];
}
}
return ans;
}
signed main()
{
cin>>m;
cin>>n;
for(int i=1;i<=m;i++)
{
cin>>a[i];
}
t=sqrt(m);
for(int i=1;i<=t;i++)
{
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<m)
{
t++;
L[t]=R[t-1]+1;
R[t]=m;
}
for(int i=1;i<=t;i++)
{
for(int j=L[i];j<=R[i];j++)
{
pos[j]=i;
sum[i]+=a[j];
}
}
for(int i=1;i<=n;i++)
{
int opt;
cin>>opt;
if(opt==1)
{
int x, y, k;
cin>>x>>y>>k;
update(x,y,k);
}
else
{
int x, y;
cin>>x>>y;
cout<<query(x,y)<<"\n";
}
}
}
线段树2就不放了。
例题
First:LOJ6278
询问:单独一块时暴力。否则不完整的块暴力,完整的块二分。
每次需要高频率还原 numnum 动态数组。
还原函数:
void resort(int x)
{
num[x].clear();
for(int i=L[x];i<=R[x];i++)
{
num[x].push_back(a[i]);
}
sort(num[x].begin(), num[x].end());
}