「学习笔记」线段树
目录
「学习笔记」线段树
Question
用一个数据结构维护一个数列,支持:
- 单点/区间修改
- 区间求和/最大值/最小值/...
DateStructure:线段树
整体思想
线段树就是一大段分成两小段,对于小段再继续分割,更新时更新父节点,查询时合并区间的查询。
操作方法
单点修改
从最大区间开始一直寻找包含修改点的区间,同时对包含此点区间进行值的修改。
区间查询
从最大区间开始一直遍历两个子区间,每个区间有三种情况:
- 与查询区间完全不相交,此时直接跳出。
- 被查询区间完全包含,此时返回此区间的值。
- 1,2都不满足,此时继续遍历两个子区间。
Lazy Tag
当进行区间修改的时候,如果一次性更新到最底层的节点时,时间就会变成 \(O(n)\) ,稳稳炸飞。
这时候我们就要打“懒标记”了。
当一个区间完全被包含在修改区间内的时候,就给他打上标记,当查询到它的子区间时再去下传标记。
Code
单点查询和更改更简单一些,会了区间肯定能实现出来,这里就只粘上区间更改和查询的板子了。
其实就是懒。
void pushdown(ll p){//下传标记
if(!t[p].bj)return;
t[p*2].bj+=t[p].bj,t[p*2].val+=t[p].bj*(t[p*2].r-t[p*2].l+1);
t[p*2+1].bj+=t[p].bj,t[p*2+1].val+=t[p].bj*(t[p*2+1].r-t[p*2+1].l+1);
t[p].bj=0;
//先打标记,再更新值
}ll build_tree(ll p,ll l,ll r){//
t[p].l=l,t[p].r=r;
if(l==r)scanf("%lld",&t[p].val);
//最底层的点
else{
ll mid=(l+r)>>1;
t[p].val=build_tree(p*2,l,mid)+build_tree(p*2+1,mid+1,r);
//建儿子
}return t[p].val;
}void update(ll p,ll l,ll r,ll d){//区间更新
ll le=t[p].l,ri=t[p].r;
if(le>r||ri<l)return;
if(le>=l&&ri<=r)t[p].val+=d*(t[p].r-t[p].l+1),t[p].bj+=d;
//打上标记
else{
pushdown(p),update(p*2,l,r,d),update(p*2+1,l,r,d);
t[p].val=t[p*2].val+t[p*2+1].val;
//给儿子下传之前的标记
}
}ll query(ll p,ll l,ll r){//查询
ll le=t[p].l,ri=t[p].r,ans;
if(le>r||ri<l)return 0;
if(le>=l&&ri<=r)return t[p].val;
pushdown(p);//下传标记
return ans=query(p*2,l,r)+query(p*2+1,l,r);
}
Example:山海经
因为教练给我们留的六道题中五道都是裸板子,所以只能直接放毒瘤题了。
Meaning of the Problem
给你一个序列,每次查询区间 \([x,y]\) 的最大子段和。
Solution
我们要维护一堆东西:
- 区间和
- 最大前缀和其右端点
- 最大子段和其左右端点
- 最大后缀和其左端点
我们要通过合并来维护子段和。
- 对于一个区间的最大前缀和有两种情况:
- 左子区间的最大前缀和。
- 左子区间和加右子区间的最大前缀和。
- 对于一个区间的最大后缀和有两种情况:
- 右子区间的最大后缀和。
- 右子区间和加左子区间的最大后缀和。
- 对于一个区间的最大子段和有三种情况:
- 左子区间的最大子段和。
- 右子区间的最大子段和。
- 左子区间的最大后缀和加右子区间的最大前缀和。
只要我们把板子里的"求和操作"改成"合并操作"就可以了。
这道题很恶心(对于线段树初学者来说),很适合锻炼代码能力,我写+调了一个早上才过,同机房的大佬们只有两个调到了晚上才过,大多数人还在调……
Code
#include <bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=1e5+10,inf=0x3f3f3f3f;
ll n,m,x,y,a[N],ans;
struct stru{
ll val,l,r;//区间与值
ll qz,qr,hz,hl;//维护前后缀
ll zz,zl,zr;//维护中缀
#define ls p<<1
#define rs p<<1|1
}t[4*N];
inline ll read(){
int x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();
return w*x;
}stru merge(stru a,stru b){//合并
stru p;//合并结果区间
//特判
if(a.l==0&&a.r==0&&b.l==0&&b.r==0)return stru{0,0,0,0,0,0,0,0,0,0};
if(a.l==0&&a.r==0)return b;
if(b.l==0&&b.r==0)return a;
//左右端点
p.l=a.l,p.r=b.r;
//区间和
p.val=a.val+b.val;
//前缀
if(a.val+b.qz>a.qz)p.qz=a.val+b.qz,p.qr=b.qr;
else p.qz=a.qz,p.qr=a.qr;
//后缀
if(b.val+a.hz<b.hz) p.hz=b.hz,p.hl=b.hl;
else p.hz=b.val+a.hz,p.hl=a.hl;
//中缀
int qjh=a.hz+b.qz;//前加后
if(a.zz>=qjh&&a.zz>=b.zz)p.zz=a.zz,p.zl=a.zl,p.zr=a.zr;
else if(qjh>=b.zz)p.zz=qjh,p.zl=a.hl,p.zr=b.qr;
else p.zz=b.zz,p.zl=b.zl,p.zr=b.zr;
return p;
}void build_tree(int p,int l,int r){
#define f t[p]
f.l=l,f.r=r;
if(l==r){
f.zz=f.val=read();
f.qz=f.hz=f.val;
f.qr=f.zl=f.zr=f.hl=l;
}else{
int mid=(l+r)>>1;
build_tree(ls,l,mid),build_tree(rs,mid+1,r);
f=merge(t[ls],t[rs]);
}
}stru query(int p,int l,int r){
int le=t[p].l,ri=t[p].r;
stru tmp=stru{0,0,0,0,0,0,0,0,0,0};
if(le>r||ri<l)return tmp;
if(le>=l&&ri<=r)tmp=t[p];
else tmp=merge(query(p*2,l,r),query(p*2+1,l,r));
return tmp;
}
int main(){
freopen("date.in","r",stdin);
n=read(),m=read();
build_tree(1,1,n);
while(m--){
x=read(),y=read();stru ans=query(1,x,y);
printf("%lld %lld %lld\n",ans.zl,ans.zr,ans.zz);
}
return 0;
}
\[\huge\mathfrak{The\ End}
\]