线段树
__Green_tick__ · · 算法·理论
线段树
前置知识
- 树
- 分治
适用背景
线段树是算法竞赛中常用的用来维护区间信息的数据结构。
线段树可以在
算法过程
线段树将每个长度不为
有个大小为
如何构建
如图:
构建如上线段树,将其放入数组:
void build(long long id,long long l,long long r){
tree[id].l=l;
tree[id].r=r;
if(l==r){//递归边界
tree[id].sum=arr[l];
return;
}else{
long long mid=(l+r)>>1;
build(id<<1,l,mid);//向下递归
build(id<<1|1,mid+1,r);
PushUp(id);
}
return;
}
区间查询
现需要查询
即
long long query(long long id,long long l,long long r){
if(l<=tree[id].l && tree[id].r<=r){
return tree[id].sum;
}else{
long long mid=(tree[id].l+tree[id].r)>>1;//取中间
long long sum=0;//当前总和
if(l<=mid) sum+=query(id<<1,l,r);//即 id/2
if(mid<r) sum+=query(id<<1|1,l,r);//即 id/2+1
return sum;
}
}
单点修改
现需要将
void change(long long id,long long p,long long k){
if(tree[id].l==tree[id].r){
tree[id].sum+=k;
return;
}else{
long long mid=(tree[id].l+tree[id].r)>>1;
if(p<=mid) change(id<<1,p,k);
else change(id<<1|1,p,k);
PushUp(id);//即 tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
return;
}
}
综合以上:
#include<bits/stdc++.h>
using namespace std;
const long long MaxN=1e5+10;
long long n,m;
long long arr[MaxN];
struct Tree{
long long l,r,sum;
}tree[MaxN<<2];
void PushUp(long long id){
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
return;
}
void build(long long id,long long l,long long r){
tree[id].l=l;
tree[id].r=r;
if(l==r){
tree[id].sum=arr[l];
return;
}else{
long long mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
PushUp(id);
}
return;
}
void change(long long id,long long p,long long k){
if(tree[id].l==tree[id].r){
tree[id].sum+=k;
return;
}else{
long long mid=(tree[id].l+tree[id].r)>>1;
if(p<=mid) change(id<<1,p,k);
else change(id<<1|1,p,k);
PushUp(id);
return;
}
}
long long query(long long id,long long l,long long r){
if(l<=tree[id].l && tree[id].r<=r){
return tree[id].sum;
}else{
long long mid=(tree[id].l+tree[id].r)>>1;
long long sum=0;
if(l<=mid) sum+=query(id<<1,l,r);
if(mid<r) sum+=query(id<<1|1,l,r);
return sum;
}
}
int main(){
cin>>n>>m;
for(long long i=1;i<=n;i++){
cin>>arr[i];
}
build(1,1,n);
for(long long i=0;i<m;i++){
long long opt,x,y;
cin>>opt>>x>>y;
if(opt==1){
change(1,x,y);
}else{
cout<<query(1,x,y)<<endl;
}
}
return 0;
}
如上代码可通过 P3374 【模板】树状数组 1。
显然,线段树可以实现树状数组所可以实现的题目。
Lazy Tag优化
懒惰标记就是在对一颗树的所有节点进行某种统一操作时,只对根节点做一个标记表示它的子树都要进行这个操作,但是懒惰标记仅可用于能够计算出若对树上的每个节点进行操作时,能够通过根节点直接算出查询值。这可能有些复杂。
举个栗子
灰色为 Lazy Tag 标记。
执行下一步:
将
执行下一步:
灰色为 Lazy Tag 标记。
如何实现
如何下放 Lazy Tag
void PushDown(long long id){
if(!tree[id].LazyTag){//有标记
return;
}else{
tree[id<<1].LazyTag+=tree[id].LazyTag;//左子树下放标记
tree[id<<1|1].LazyTag+=tree[id].LazyTag;//右子树下放标记
long long mid=(tree[id].l+tree[id].r)>>1;
tree[id<<1].sum+=(mid-tree[id].l+1)*tree[id].LazyTag;
tree[id<<1|1].sum+=(tree[id].r-mid)*tree[id].LazyTag;
tree[id].LazyTag=0;//清空当前标记
}
return;
}
本代码需加在涉及查询、修改之前。
综合以上:
#include<bits/stdc++.h>
using namespace std;
const long long MaxN=1e5+10;
long long n,m;
long long arr[MaxN];
struct Tree{
long long l,r,sum;
long long LazyTag;
}tree[MaxN<<2];
void PushUp(long long id){
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
return;
}
void PushDown(long long id){
if(!tree[id].LazyTag){
return;
}else{
tree[id<<1].LazyTag+=tree[id].LazyTag;
tree[id<<1|1].LazyTag+=tree[id].LazyTag;
long long mid=(tree[id].l+tree[id].r)>>1;
tree[id<<1].sum+=(mid-tree[id].l+1)*tree[id].LazyTag;
tree[id<<1|1].sum+=(tree[id].r-mid)*tree[id].LazyTag;
tree[id].LazyTag=0;
}
return;
}
void build(long long id,long long l,long long r){
tree[id].l=l;
tree[id].r=r;
if(l==r){
tree[id].sum=arr[l];
return;
}else{
long long mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
PushUp(id);
}
return;
}
void change(long long id,long long l,long long r,long long k){
if(l<=tree[id].l && tree[id].r<=r){
tree[id].LazyTag+=k;
tree[id].sum+=(tree[id].r-tree[id].l+1)*k;
return;
}else{
PushDown(id);
long long mid=(tree[id].l+tree[id].r)>>1;
if(l<=mid) change(id<<1,l,r,k);
if(mid<r) change(id<<1|1,l,r,k);
PushUp(id);
}
return;
}
long long query(long long id,long long l,long long r){
if(l<=tree[id].l && tree[id].r<=r){
return tree[id].sum;
}else{
PushDown(id);
long long mid=(tree[id].l+tree[id].r)>>1;
long long sum=0;
if(l<=mid) sum+=query(id<<1,l,r);
if(mid<r) sum+=query(id<<1|1,l,r);
return sum;
}
}
int main(){
cin>>n>>m;
for(long long i=1;i<=n;i++){
cin>>arr[i];
}
build(1,1,n);
for(long long i=0;i<m;i++){
long long opt;
cin>>opt;
if(opt==1){
long long l,r,k;
cin>>l>>r>>k;
change(1,l,r,k);
}else{
long long x,y;
cin>>x>>y;
cout<<query(1,x,y)<<endl;
}
}
return 0;
}
可通过 P3372 【模板】线段树 1。