分块用来解决什么问题
分块是用来解决区间和单点的修改和查询等问题的。
拿到这种问题大家第一反应就是用一些数据结构去维护,如线段树和树状数组
mlogn
m
log
n
的时间内解决。分块有着惊人短的代码量,和极其简单的代码思想。。比线段树好debug多了。
分块的数据储存模式
对于一个长度为
n
n
的序列,我们把它分成左右块,并且使这
n−−√
n
个块的数量基本相同。
我们怎么确定第
i
i
个属于第几个块呢?
我们设
第
i
i
个数是第块的。
我们来看一看数是怎么分布的:
我们可以发现,这样的存储方式,除了最后一块,其他块必定大小都相等,且这些块的大小都是
q
q
。
而且有一个很奇怪的结论:块的数量和数的数量不一定成正相关。
对于第个数,它在第
(i−1+q)/q
(
i
−
1
+
q
)
/
q
块;
对于第
i
i
个块,它的数据范围是第个数到第
i∗q
i
∗
q
个数。
分块的运作方式
我们先来考虑两种最简单的操作:区间修改和单点查询。
区间修改
我们回忆线段树的修改操作:把区间
[l,r]
[
l
,
r
]
不断沿着线段树分解,对于每一层分解,都会出现,线段树的某些线段被该区间完美覆盖,但是区间左右都会有多余部分。线段树的解决方法是对这两个多余部分进行继续分解。但是分块我们只会分解一次,对于这些多余部分,我们就直接暴力枚举;对于那些完美覆盖的块,我们将会采用一个类似于
lazy
l
a
z
y
标记的东西,来标记整体偏移量。
例如
del[i]=2
d
e
l
[
i
]
=
2
就代表块
i
i
所有的元素都+2。
查询时间复杂度
单点查询
单点查询,只要把原数组的数值加上当前数所在块的 del d e l 偏移量值就行了。查询复杂度 O(1) O ( 1 )
时间复杂度
设修改
c
c
次,查询次,那么它的时间复杂度就是
O(cn−−√+q)
O
(
c
n
+
q
)
。
对比线段树,线段树的时间复杂度是
O((c+q)logn)
O
(
(
c
+
q
)
log
n
)
。
但是代码复杂度确实是分块小很多!!
例题1
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值。
输入格式
第一行输入一个数字 n 第二行输入 n 个数字,第 i 个数字为a[i],以空格隔开 接下来输入 n 行询问,每行输入四个数字 opt, l,r,c, 以空格隔开 若 opt = 0,表示将 [l,r] 的之间的数都加 c 若 opt = 1,表示询问 a[r] 的值(l 和 c 忽略)
输出格式
对于每次询问,输出一行一个数字表示答案
样例数据
input
4
1 2 2 3
0 1 3 1
1 0 1 0
0 1 2 2
1 0 2 0
output
2
5
题解
板子题啊。
我们用分块做。
直接上代码
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int num=0;
char c=' ';
bool flag=true;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
return flag ? num : -num;
}
const int maxn=50020;
int n,q,opt,l,r,c;
int a[maxn],p[maxn],del[maxn];
void init()
{
n=read();q=sqrt(n);
for(int i=1;i<=n;i++)
a[i]=read(),p[i]=(i-1+q)/q;
//a是原数组
//p是第i个数在哪一块
//del是第i块的偏移量
}
void add()
{
for(int i=l;i<=min(r,p[l]*q);i++)
//这边的min(r,p[l]*q)代表的意思可以结合上面数据范围来看。
{
a[i]+=c;
}//处理l所在的块
if(p[r]!=p[l])//如果不在同一块,那就处理r在的块
for(int i=(p[r]-1)*q+1;i<=r;i++)
a[i]+=c;
for(int i=p[l]+1;i<p[r];i++)
del[i]+=c;//处理l块和r块之间的块
}
int main()
{
init();
for(int i=1;i<=n;i++)
{
opt=read();
l=read();
r=read();
c=read();
if(opt==0)
{
add();
}
else
{
printf("%d\n",del[p[r]]+a[r]);
//r所在的块的偏移量+r原来的数值。
}
}
return 0;
}