线段树全家桶(持续咕咕咕)
线段树全家桶
线段树基础
原理:
将一个大区间划分为若干个小区间,每一个小区间都对应这线段树中的一个结点。(分治的思想)
这个线段树采用二倍编号规则。
即 一个结点的左儿子为这个结点的编号的两倍,右儿子为两倍加一。
结构体定义:
#define lc x<<1
#define rc x<<1|1
struct node{
int l,r;
ll sum,lazy;
#define l(x) c[x].l
#define r(x) c[x].r
#define sum(x) c[x].sum
#define lazy(x) c[x].lazy
}c[400005];
基本操作(无push_down操作):
以下均以求区间和为例子
更新结点值(update 又称 push _up )
inline void update(int x){
sum(x)=sum(lc)+sum(rc);
}
建树(build )
inline void build(int x,int l,int r){
l(x)=l;
r(x)=r;
if(l==r) {//已经到了叶子结点
sum(x)=a[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
update(x);
}
单点修改
就是简单的修改叶子结点然后回溯修改父亲结点的值
inline void modify(int x,int L,int R,int t){
if(l(x)==L&&r(x)==R){
sum(x)+=t;
return;
}
int mid=(l(x)+r(x))>>1;
if(mid>=L){modify(lc,L,R,t);}
if(mid<R){modify(rc,L,R,t);}
update(x);
return;
}
区间求和:
要求查询一个区间[L,R] 如何求出$ \sum_{i=L}^R {a_i}
int sum=0;
for(int i=L;i<=R;++i){
sum+=a[i];
}
return sum;
2.线段树的查询方法:
可以表示为
对于一个区间: 它有四种情况: 1.它 完全包含 于这个区间 对于这种情况 我们直接
return sum(x);
2.左儿子有一部分包含于这个区间
if(mid>=L){ans+=query(lc,L,R);}
3.右儿子有一部分包含于这个区间
if(mid<R){ans+=query(rc,L,R);}
. .4.它根本不在查询区间中
return 0;
这样子就可以写出查询代码了
inline ll query(int x,int L,int R){
if(t[x].l>=L&&t[x].r<=R){return sum(x);}
if(t[x].l>R||t[x].r<L){return 0;}
int mid=(l(x)+r(x))>>1;
ll ans=0;
if(mid>=L){ans+=query(lc,L,R);}
if(mid<R){ans+=query(rc,L,R);}
return ans;
}
复杂度
进阶操作(push _down )
区间修改
暴力修改:
对在 [L,R] 的每一个叶子结点都进行一次单点修改操作
复杂度
线段树延迟修改 (push_down操作,懒标记维护)
对于一个区间:
你在修改的时候修改完了,后续的查询却根本没有用到这个区间,那你的这次修改就是无用的。
所以我们可以在一次修改中,只修改目标修改区间中的多个最大子区间,修改这个区间的
比如:
当我们修改区间
修改:值修改区间的
这样我们在区间修改时就可以做到严格
push _down 操作
那么我们可以
对于每一个元素
查找
将
整个复杂度(
通常的暴力都是
而线段树就是用来优化的这个查询过程
遇到一个
查询
然后在线段树中将
听起来就很简单
扫描线
我不会(或者说我一直没有过板子题)先咕咕咕好了
各种进阶例题:
区间开方操作:
P4145 上帝造题的七分钟2 / 花神游历各国
题目描述:
第一行一个整数
第二行
第三行一个整数
接下来
易证昂,所有
因为
所以当我们修改到一个区间全为
于是我们在整个区间开方的过程中维护两个变量:
一个
因为开方并不满足区间可加性,我们可以转而使用复杂度略高的进行
每一次的复杂度为(
但是每一个点最多修改6次,所以是可以通过这道题的
Code:
#define ll long long
#define lc x<<1
#define rc x<<1|1
struct node{
int l,r;
ll sum,maxinum;
#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum
#define maxinum(x) t[x].maxinum
}t[4000005];
int n,m;
ll a[100005];
inline void update(int x){
sum(x)=sum(lc)+sum(rc),maxinum(x)=max(maxinum(lc),maxinum(rc));
return;
}
inline void build(int x,int l,int r){
l(x)=l;
r(x)=r;
if(l==r){
sum(x)=a[l];
maxinum(x)=a[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
update(x);
}
inline void modify(int x,int L,int R){
if(l(x)==r(x)){
sum(x)=sqrt(sum(x));
maxinum(x)=sqrt(maxinum(x));
return;
}
int mid=(l(x)+r(x))>>1;
if(mid>=L&&maxinum(lc)>1){modify(lc,L,R);}
if(mid<R&&maxinum(rc)>1){modify(rc,L,R);}
update(x);
return;
}
inline ll query(int x,int L,int R){
if(l(x)>=L&&r(x)<=R){return sum(x);}
ll tep=0;
int mid=(l(x)+r(x))>>1;
if(mid>=L){tep+=query(lc,L,R);}
if(mid<R){tep+=query(rc,L,R);}
return tep;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){scanf("%lld",&a[i]);}
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;++i){
int op,L,R;
scanf("%d%d%d",&op,&L,&R);
if(L>R){swap(L,R);}
if(op==0){modify(1,L,R);}
else{printf("%lld\n",query(1,L,R));}
}
return 0;
}