一.线段树引入.
线段树,是一种基于分治思想的高级树形数据结构,可以支持满足快速合并性质的信息维护.
线段树作为较为容易也较传统的高级数据结构,应用广泛,在高级数据结构中占有重要的地位.
二.线段树概述.
我们先来回忆一下分治,分治的一个应用就是想要得到一个区间的值,这个值不能直接求得,但是可以借助和来快速得到,而且对于任意一个x,的值也可以快速得到,那么就可以通过分治来求得.
一个经典的算法就是归并排序,它要排序区间,那么就先排好区间和,合并区间和已经排好的两个序列.然后也不能直接排好,就把这个区间也通过两个子区间的信息合并...直到区间变成了,这个区间直接有序了,就可以直接返回求得排好序的序列.
那么我们把上述分治的过程画成一张图(假设是区间)的话:
我们发现这其实是一棵树,而且我们可以把这个东西显式建树,就可以得到一棵线段树.
三.基本操作Merge.
既然一个区间的信息要通过区间和的信息合并来得到,那么自然要写一个函数来实现这个功能了.
虽然一般来说,这个函数的代码量通常很小,都不会单独写一个函数,但是在这里还是写一下吧,这里以维护区间和为例:
tree Merge(tree a,tree b){
tree c;
c.l=a.l;c.r=b.r;
c.sum=c.sum+b.sum;
return c;
}
四.建树Build.
建树的过程其实就是一个分治,然后把每一个节点的信息存下来.
这里我们依然以维护区间和为例,设a数组为初始的值的情况,那么就分治到叶子(区间满足),就把信息直接存到叶子上,其它节点的信息就通过两个儿子的信息合并就可以了.
由于我们的分治就是将所有点操作了一遍,而这棵完全二叉树的节点个数为2n-1,所以建树是的.
具体代码如下:
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}
五.单点操作.
我们考虑一个单点操作,比如说单点查询一个点x的值.这个应该比较容易,可以从根开始不断往下递归找到叶子节点,然后直接返回这个叶子的信息即可.
我们在考虑一个单点修改的操作,比如说给一个点x加上v,那么我们可以找到叶子,讲叶子的信息直接加上v.但是叶子发生改变后,会使该叶子的所有祖先的信息发生改变,所以我们要在回溯的时候,再让经过的节点进行merge操作.
由于每次操作最多只会经过一条从根到叶子的链,长度为树高,而树高为,所以单点操作的时间复杂度为.
具体代码如下:
int Query(int x,int k=1){
if (tr[k].l==tr[k].r) return tr[k].sum;
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) return Query(x,k<<1);
else return Query(x,k<<1|1);
}
void Add(int x,int num,int k=1){
if (tr[k].l==tr[k].r){
tr[k].sum+=num;
return;
}
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) Add(x,num,k<<1);
else Add(x,num,k<<1|1);
tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}
六.区间查询操作.
对于区间查询信息并的操作,我们当然可以利用上面单点查询每一个点,然后将信息并起来,但是这样就会使时间复杂度变为,十分不优美.
我们考虑从根节点向下的时候,若要查询的区间包括在一个节点的左儿子,直接往左儿子向下;右儿子同理;若左右儿子都有,则左右儿子都递归进入,最后将两个儿子中找到的答案合并起来就行了.
但是这样的时间复杂度貌似还是,但其实我们可以证明这是的.
证明如下:
首先,这个区间在每一层,都只会遍历到最多4个节点.
若会遍历到4个以上节点,则必定有两个节点表示的区间是直接被包含且是兄弟的关系,那么这两个节点应该会在上一层就被遍历过了,不会在这一层再被遍历了.
所以,每一层最多会遍历到4个节点,所以时间复杂度为.
证毕.
那么区间查询代码如下(以区间加为例):
int Query(int L,int R,int k=1){
if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) return Query(L,R,k<<1);
else if (L>mid) return Query(L,R,k<<1|1);
else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}
七.区间修改与lazy-tag.
区间修改,我们以给区间的每个数加上y为例.
我们考虑与区间查询类似,直接找到每一个需要修改的修改,然后对每一个区间直接加上y*区间长度.
但是这样子的话,被修改的节点下所有节点都要修改,但这样做就会无法修改,暴力修改又太慢,我们该怎么办?
我们引入lazy-tag的概念,给每一个修改的节点在打上一个tag标记,表示这个节点下面的所有节点都要增加一个值.
那么,当我们要查询点的时候,对所有经过的节点的tag往下推,对下面的节点进行修改,我们把这个过程写成一个函数pushdown.
那么我们就可以做到修改啦,但是要注意若有tag,最好在任何操作经过任何节点都pushdown.
代码如下:
void Update_add(int k,int num){ //为了简洁写一个Update函数
tr[k].sum+=num*(tr[k].r-tr[k].l+1);
tr[k].tag+=num;
}
void Pushdown(int k){
Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
tr[k].tag=0;
}
void Add(int L,int R,int num,int k=1){
if (L==tr[k].l&&R==tr[k].r){
Update_add(k,num);
return;
}
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) Add(L,R,num,k<<1);
else if (L>mid) Add(L,R,num,k<<1|1);
else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1);
tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}
八.例题与代码.
现在我们拿出三道例题,分别为luogu3372,luogu3374,luogu3368.
luogu3372代码:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m;
LL a[N+9];
struct tree{
int l,r;
LL sum,tag;
}tr[N*4+9];
void Update_add(int k,LL num){
tr[k].sum+=num*(tr[k].r-tr[k].l+1);
tr[k].tag+=num;
}
void Pushdown(int k){
Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
tr[k].tag=0LL;
}
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void Add(int L,int R,LL num,int k=1){
if (L==tr[k].l&&R==tr[k].r){
Update_add(k,num);
return;
}
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) Add(L,R,num,k<<1);
else if (L>mid) Add(L,R,num,k<<1|1);
else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL Query(int L,int R,int k=1){
if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) return Query(L,R,k<<1);
else if (L>mid) return Query(L,R,k<<1|1);
else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]);
}
Abigail work(){
Build(1,n);
}
Abigail getans(){
int opt,l,r;
LL x;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
if (opt==1){
scanf("%d%d%lld",&l,&r,&x);
Add(l,r,x);
}else{
scanf("%d%d",&l,&r);
printf("%lld\n",Query(l,r));
}
}
}
int main(){
into();
work();
getans();
return 0;
}
luogu3374代码:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m;
LL a[N+9];
struct tree{
int l,r;
LL sum;
}tr[N*4+9];
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void Add(int x,LL num,int k=1){
if (tr[k].l==tr[k].r){
tr[k].sum+=num;
return;
}
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) Add(x,num,k<<1);
else Add(x,num,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL Query(int L,int R,int k=1){
if (tr[k].l==L&&tr[k].r==R) return tr[k].sum;
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) return Query(L,R,k<<1);
else if (L>mid) return Query(L,R,k<<1|1);
else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]);
}
Abigail work(){
Build(1,n);
}
Abigail getans(){
int opt,l,r;
LL x;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
if (opt==1){
scanf("%d%lld",&l,&x);
Add(l,x);
}else{
scanf("%d%d",&l,&r);
printf("%lld\n",Query(l,r));
}
}
}
int main(){
into();
work();
getans();
return 0;
}
luogu3368代码:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m;
LL a[N+9];
struct tree{
int l,r;
LL sum,tag;
}tr[N*4+9];
void Update_add(int k,LL num){
tr[k].sum+=num*(tr[k].r-tr[k].l+1);
tr[k].tag+=num;
}
void Pushdown(int k){
Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
tr[k].tag=0;
}
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void Add(int L,int R,LL num,int k=1){
if (tr[k].l==L&&tr[k].r==R){
Update_add(k,num);
return;
}
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) Add(L,R,num,k<<1);
else if (L>mid) Add(L,R,num,k<<1|1);
else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL Query(int x,int k=1){
if (tr[k].l==tr[k].r) return tr[k].sum;
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) return Query(x,k<<1);
else return Query(x,k<<1|1);
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]);
}
Abigail work(){
Build(1,n);
}
Abigail getans(){
int opt,l,r;
LL x;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
if (opt==1){
scanf("%d%d%lld",&l,&r,&x);
Add(l,r,x);
}else{
scanf("%d",&l);
printf("%lld\n",Query(l));
}
}
}
int main(){
into();
work();
getans();
return 0;
}