题解:P3368 【模板】树状数组 2
目录
- 前言
- 算法介绍
- 正确性证明
- 代码实现
- 后记
前言
本篇由 easy42 编写。
这是一篇数据结构分块题解。
算法介绍
分块,是一种暴力数据结构。它能以比线段树差,比暴力好的复杂度解决问题,时间复杂度通常是
分块与线段树的不同之处是:分块是把序列分为一个个小块,而线段树则把序列建成一颗二叉树,每个节点存储区间值。
线段树一般只能解决具有合并性的问题,然而分块可以更灵活,更暴力的解决问题。
而且分块编码相对较为简单,在考场上可以发挥优势。
正确性证明
分块的思想可以概述为:整块统一处理,散块暴力统计。
分块的步骤是这样的:先把序列分成长度为
如果
如果
对于每个块,维护它的修改标记,以及序列中每个数的值。
这题分为两个操作:
- 区间修改
- 单点查询
在区间修改时,区间
在单点查询时,直接把原数组的元素加上这个块的修改标记就好了。
分析一下,我们既要遍历所有块的元素,也要遍历序列中所有的块,此时的时间复杂度为
代码实现
初始化
首先,要确定把块的长度
int block=sqrt(n);//块长
块的个数
int t=n/block;//块的个数
if(n%block) t++;//增加散块
需要维护每个块的开头与结尾。
for(int i=1;i<=t;i++){
head[i]=(i-1)*block+1;//是上个块的开头加一
tail[i]=i*block;
}
请注意,最后一个块的末尾不能超过
tail[t]=n;
以及使用数组
for(int i=1;i<=n;i++){
cnt[i]=(i-1)/block+1;
}
初始化完成。
区间修改
如果在同一块内,暴力修改即可。
if(cnt[x]==cnt[y]){
for(int i=x;i<=y;i++){
a[i]+=k;
}
}
如果不在同一块内,先处理整块,再处理散块。
如果是整块,直接修改标记。
for(int i=cnt[x]+1;i<=cnt[y]-1;i++){
add[i]+=k;
}
散块暴力更新:
for(int i=x;i<=tail[cnt[x]];i++){
a[i]+=k;
}
for(int i=head[cnt[y]];i<=y;i++){
a[i]+=k;
}
单点查询
直接把当前值加上块的修改标记就行了。
cout<<a[x]+add[cnt[x]]<<endl;
总代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,add[1000005],a[1000005],head[1000005],tail[1000005],cnt[1000005];
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int block=sqrt(n);
int t=n/block;
if(n%block) t++;
for(int i=1;i<=t;i++){
head[i]=(i-1)*block+1;
tail[i]=i*block;
}
tail[t]=n;
for(int i=1;i<=n;i++){
cnt[i]=(i-1)/block+1;
}
while(m--){
int op;
cin>>op;
if(op==1){
int x,y,k;
cin>>x>>y>>k;
if(cnt[x]==cnt[y]){
for(int i=x;i<=y;i++){
a[i]+=k;
}
}
else{
for(int i=cnt[x]+1;i<=cnt[y]-1;i++){
add[i]+=k;
}
for(int i=x;i<=tail[cnt[x]];i++){
a[i]+=k;
}
for(int i=head[cnt[y]];i<=y;i++){
a[i]+=k;
}
}
}
else{
int x;
cin>>x;
cout<<a[x]+add[cnt[x]]<<endl;
}
}
return 0;
}
后记
点个赞吧!