分块
分块其实算是一种思想:把数据划分成几块,在块上预处理信息,询问时可以直接用。
举个栗子:
如果要在序列中询问一段区间的和,并且可能有增加区间的值。
可以将序列分成大小为根号
n
n
n 的块,预处理每块的和。
对于每个序列里的值,记录他们自己的值和所属的块。
对于每个块,维护左右区间和和预处理的和。
如果遇到查询:
可以直接将
l
,
r
l,r
l,r 中间的整块的和加上(图里的圈2和圈3)。
再暴力统计
l
l
l 到后面第一个整块和
r
r
r 前面第一个整块中间的值。(图里的圈1和圈4)
图画的不咋地多多包容
如果遇到修改:
同样的,可以直接将
l
,
r
l,r
l,r 中间的整块的预处理的和加上要增加的值。
(所以查询散块的时候不要忘记把这部分加上)
再暴力把
l
l
l 右侧,
r
r
r 左侧的散块原序列的变量加上要增加的值。
分块的时间复杂度和分的块的大小有密切关系,一般会设为
n
\sqrt{n}
n ,单次操作最坏是
O
(
n
/
b
+
b
)
O(n/b+b)
O(n/b+b)(
b
b
b为块长)。总复杂度是
O
(
m
n
)
O(m\sqrt{n})
O(mn)。(等我做到块长是根n过不了的题就把题放过来,顺便讲一下,鸽)
可以用 P3372 来简单练习一下
就是区间加和区间修改
code:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=100005;
int n,m;
long long kid[N],ak[N],sum[N],a[N];//kid是块的左边界,ak记录每个数所属的块,sum是每个块预处理的和
int siz;
long long k_add[N];//k_add是每个块单独加了多少数,和sum的区别是sum比k_add多乘了一个size
void csh()//预处理
{
for(int i=1;i<=n/siz+10;i++)
{
kid[i]=min(n+1,(i-1)*siz+1);
sum[i]=0;
}
for(int i=1;i<=n;i++)
{
ak[i]=(i-1)/siz+1;
}
for(int i=1;i<=n;i++)
{
sum[ak[i]]+=a[i];
}
}
void add(int l,int r,int x)//修改
{
int k_l=ak[l],k_r=ak[r];
if(k_l==k_r)//同一个块
{
for(int i=l;i<=r;i++)
{
a[i]+=x;
sum[k_l]+=x;
}
return ;
}
for(int i=k_l+1;i<k_r;i++)//整块的
{
k_add[i]+=x;
sum[i]+=x*(kid[i+1]-kid[i]);
}
for(int i=l;i<kid[k_l+1];i++)//l的散块
{
a[i]+=x;
sum[k_l]+=x;
}
for(int i=kid[k_r];i<=r;i++)//r的散块
{
a[i]+=x;
sum[k_r]+=x;
}
}
long long query(int l,int r)//查询
{
int k_l=ak[l],k_r=ak[r];
long long ans=0;
if(k_l==k_r)
{
for(int i=l;i<=r;i++)
{
ans+=k_add[k_l]+a[i];
}
return ans;
}
for(int i=k_l+1;i<k_r;i++)
{
ans+=sum[i];
}
for(int i=l;i<kid[k_l+1];i++)
{
ans+=k_add[k_l]+a[i];
}
for(int i=kid[k_r];i<=r;i++)
{
ans+=k_add[k_r]+a[i];
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
siz=500;
csh();
int t,x,y,k;
for(int i=1;i<=m;i++)
{
scanf("%d",&t);
if(t==1)
{
scanf("%d%d%d",&x,&y,&k);
add(x,y,k);
}else
{
scanf("%d%d",&x,&y);
printf("%lld\n",query(x,y));
}
}
return 0;
}
谢谢各位大佬看完QwQ(未来会补充很多,现在是极简版/hanx)