分块算法用于解决区间的查询问题。
分块算法,故名思意,就是把一个数组分为长度为 n \sqrt{n} n的数个小块,对每个小块进行单独的维护。
需要用到的数组有:
ll st[1000100],ed[1000100],a[1000100];
ll sum[1000100],add[1000100],pos[1000100];
以上数组从上往下从左到右依次为:每一个区块在a中的的首下标和末下标,原数组a;更改之后的第i个块的总值,对整个块进行更新的值,a中每一个元素所在的块的标号。(具体作用会在下面解释)
在开始查询操作之前,需要先对部分数组进行预处理:将块划分好,并将每个块的sum值更新。
for(ll i = 1; i <= n; i++)
{
cin>>a[i];
pos[i] = (i-1)/len+1;
sum[(i-1)/len+1]+=a[i];
}
ll maxx = n%len?n/len+1:n/len;//maxx代表分出的总块数
for(ll i = 1; i <= maxx; i++)
st[i] = (i-1)*len+1,ed[i] = i==maxx?n:i*len;
在进行区间更改时,需要判断给出的区间
[
l
,
r
]
[l,r]
[l,r]是否在一个块内,如果是,则需要对这个块的所有元素进行单独更改,再将更改后的值更新到sum数组里。
否则,就对跨过的所有整块进行更新,此时用add数组来表示每个整块中每个数更新的值,同时对于那些处于两端的不包含整个小块的区间中的数做单独处理,并将处理后的值更新到sum数组中。
void change(ll l,ll r,ll w)
{
ll p = pos[l],q = pos[r];
if(p == q)
{
for(ll i = l; i <= r; i++)
{
a[i] += w;
}
for(int i = l; i <= r; i++)sum[i]+=a[i];
}
else
{
for(ll i = p+1; i <= q-1; i++)add[i]+=w;
for(ll i = l; i <= ed[p]; i++)a[i]+=w;
for(ll i = st[p]; i <= ed[p]; i++)sum[p] += a[i];
for(ll i = st[q]; i <= r; i++)a[i]+=w;
for(ll i = st[q]; i <= ed[q]; i++)sum[q] += a[i];
}
}
在进行区间查询时,同样需要对其进行区间更改时的判断。
对于第i块,它的总值为sum[i]+add[i],而对于那些非整块,它的总值为各元素相加同时再加上每个元素对应的小块所对应的add*元素的数量。
ll ans = 0;
ll p = pos[l],q = pos[r];
if(p == q)
{
for(int i = l; i <= r; i++)ans+=a[i];
ans+=(r-l+1)*add[p];
}
else
{
ll num = 0;
for(ll i = p+1; i <= q-1; i++)
{
ans += sum[i]+add[i]*len;//len为每个小块的长度
}
for(int i = l; i <= st[p]; i++)ans+=a[i];
ans+=(st[p]-l+1)*add[p];
for(int i = st[q]; i <= r; i++)ans+=a[i];
ans+=(r-st[q]+1)*add[q];
}