Get 新专题 —— 数列分块
来和写篇blog
其实这是以前讲过的......笔者没认真听......现在复习算是明白了......
数列分块板子题 - LOJ 6277 数列分块入门1
挂上链接 - https://loj.ac/problem/6277
题面很简单 - 数列的区间加法,单点查值
正文部分
数列长度,操作数量最大可达50000,单点查值问题不大,可区间加法就太坑了,如果硬做,时间复杂度可达O(n^2),肯定超时......
众所周知,操作越宏观复杂度越低。不难发现,区间加法一个一个加太过微观,要设法往宏观拔。分块就是一种方法。
顾名思义,分块就是把数列分成若干块。怎么分呢?设块数为a,一块数量为b,总量为n。我们希望a与b相对平衡(原因一会就会明了)。根据a与b的数量关系,即a*b=n,为使其平衡,不妨令a,b都为sqrt(n)(根号n)。
举个栗子:
分完块后就可以开始操作了
先来理解一下分块。分块的精髓,即"大段维护,小段朴素"。大段即整段,也就是一个分块,小段即操作区间两端不满一块的部分。维护即整段操作,朴素即原始的一个数一个数的操作。
对于这题,区间加法就可以运用"大段维护,小段朴素"。将操作区间分为"大段","小段"。
"大段"操作,扫描每一个"大段",加到一个数中,将加一个一个加化为整体加,将微观化为宏观。
for(int i=p[l]+1; i<=p[r]-1; i++){
atag[i]+=c; //大段统一加
}
"小段"一般有两段,即左端和右端,分别一个数一个数的加
for(int i=l; i<=min(p[l]*sqrtN,r); i++)
a[i]+=c; //左边的不满一块的部分进行"朴素"
if(p[l]!=p[r])
for(int i=(p[r]-1)*sqrtN+1; i<=r; i++)
a[i]+=c; //右边
因为大段数量不会超过sqrt(n),小段中的数的总数也不会超2*sqrt(n),所以区间加法的总复杂度从O(n^2)级别降到了O(n*sqrt(n))级别
单点查询中单点的值=其单独的值+所在区间的统一加的值
a[R]+atag[p[R]]
那么,分块1到这就基本结束啦,下面会附上完整代码(C++)和精心设计的注解方便理解
完整代码:
// LOJ 6277 - 数列分块入门 1
// https://loj.ac/problem/6277
// 分块
#include<bits/stdc++.h>
#define M 50005
using namespace std;
int sqrtN;
int a[M], atag[M], p[M];
//a[] - 数列
//atag[] - 区间统一加值
//p[] - 数列中对应下标的数所在的区间下标
void add(int l, int r, int c){//l为区间左坐标,r为右坐标 ,c为加数
for(int i=l; i<=min(p[l]*sqrtN,r); i++){
a[i]+=c; //左边的不满一块的部分进行"朴素"
}
if(p[l]!=p[r]){
for(int i=(p[r]-1)*sqrtN+1; i<=r; i++){
a[i]+=c; //右边
}
}
for(int i=p[l]+1; i<=p[r]-1; i++){
atag[i]+=c; //大段统一加
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
int N;
cin >> N;
sqrtN=sqrt(N);
for(int i=1; i<=N; i++){
cin >> a[i];
atag[i]=0;
p[i]=(i-1)/sqrtN+1;
}//预处理
for(int i=1; i<=N; i++){
int F, L, R, C;
cin >> F >> L >> R >> C;
if(F==0) add(L, R, C); //区间加法
if(F==1) cout << a[R]+atag[p[R]] << endl;//单点查值
}
return 0;
}
如有疑问欢迎在下方评论区提出
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。