线段树(Segment Tree)简介

来源:互联网 发布:阿里云怎么代理加盟 编辑:程序博客网 时间:2024/06/05 09:39

基本概念

线段树(segment tree)是一种二叉搜索树,它的每一个结点对应着一个区间[l,r],叶子结点对应的是一个单位区间,即l==r。对于一个非叶子结点[l,r],它的左儿子所表示的区间为[l,(l+r)/2],右儿子表示的区间为[(l+r)/2+1,r]。根据定义,线段树是一颗平衡二叉树,它的叶子结点的数目为N,即整个区间的长度。

例如,区间[1,10]的线段树如下图所示:

这里写图片描述

由于线段树是一颗平衡二叉树,所以它的高度为log级别,这是线段树时间复杂度良好的基础。

线段树的用途较广,主要用于更新查询。这里的更新和查询一般至少有一个是指区间的更新或查询。由于更新和查询的方法种类比较多,这也决定了线段树的灵活性,针对不同的问题,线段树处理的方式不尽相同。


下面是一个简单的例子:
维护一个数列,每次进行两种操作:

1.修改一个元素
2.查询一段区间的最大值

这是一个经典的RMQ(range minimum/maximum query)问题,用线段树该如何解决?这里更新是点更新,查询是区间查询。

基本操作

可以首先对原序列建立一颗线段树,然后对于更新操作,则对线段树的相应结点进行更新,对于查询操作则进行查询。具体操作如下:

建树:每个节点维护节点所代表区间的左右端点和该区间的信息(上面例子里就是最值)。建树时,如果到了叶子结点,那么这个结点的最值信息就是对应位置数组中的该元素值,否则递归地建左子树和右子树,然后将当前结点的区间最值设置为自己左子树和右子树的较优最值

修改:一样递归调用,从根节点开始递归到叶子结点并修改叶子结点。回溯时对路径上相应结点的最值进行更新。

查询:还是递归调用。从根节点开始递归查询,如果查询区间在该节点的左子树内,则查询左子树;如果在右子树内,则查询右子树;否则,查询左子树相应区间和右子树相应区间,并将两者的返回值信息(上面例子里就是较大值)返回。


基本操作实现

单点加减 询问区间和

#include<iostream>#include<cstdio>#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1using namespace std;const int maxn=200010;int sum[maxn<<2];void PushUp(int rt){    sum[rt]=sum[rt<<1]+sum[rt<<1|1];}void build(int l,int r,int rt){    if(l==r){        scanf("%d",&sum[rt]);        return ;    }    int m=(l+r)>>1;    build(lson); build(rson);    PushUp(rt);}void update(int p,int add,int l,int r,int rt){    if(l==r){        sum[rt]+=add;//这里是加减        return ;    }    int m=(l+r)>>1;    if(p<=m) update(p,add,lson);    else update(p,add,rson);    PushUp(rt);}int query(int L,int R,int l,int r,int rt){    if(L<=l&&r<=R) return sum[rt];    int m=(l+r)>>1;    int ret=0;    if(L<=m) ret+=query(L,R,lson);//这里是求和,也可以维护最值    if(R>m) ret+=query(L,R,rson);    return ret;}

单点覆盖 询问区间最值

#include<iostream>#include<cstdio>#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1using namespace std;const int maxn=200010;int maxx[maxn<<2];void PushUp(int rt){    maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);}void build(int l,int r,int rt){    if(l==r){        scanf("%d",&maxx[rt]);        return ;    }    int m=(l+r)>>1;    build(lson); build(rson);    PushUp(rt);}void update(int p,int cov,int l,int r,int rt){    if(l==r){        maxx[rt]=cov;//这里是赋值        return ;    }    int m=(l+r)>>1;    if(p<=m) update(p,cov,lson);    else update(p,cov,rson);    PushUp(rt);}int query(int L,int R,int l,int r,int rt){    if(L<=l&&r<=R) return maxx[rt];    int m=(l+r)>>1;    int ret=0;//较小数    if(L<=m) ret=max(ret,query(L,R,lson));    if(R>m) ret=max(ret,query(L,R,rson));    return ret;}

区间加减 询问区间和(区间最值同理)

#include<iostream>#include<cstdio>#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1using namespace std;const int maxn=200010;typedef long long LL;LL add[maxn<<2];//兼顾lazy标记与具体变化LL sum[maxn<<2];void PushUp(int rt){    sum[rt]=sum[rt<<1]+sum[rt<<1|1];}void PushDown(int rt,int len){    if(add[rt]){        add[rt<<1]+=add[rt];//下放,注意是+=而不是直接赋值        add[rt<<1|1]+=add[rt];        sum[rt<<1]+=add[rt]*(len-(len>>1));        sum[rt<<1|1]+=add[rt]*(len>>1);        add[rt]=0;//下放了,清除标记    }}void build(int l,int r,int rt){    add[rt]=0;//初始化    if(l==r){        scanf("%lld",&sum[rt]);        return ;    }    int m=(l+r)>>1;    build(lson); build(rson);    PushUp(rt);}void update(int L,int R,int c,int l,int r,int rt){    if(L<=l&&r<=R){        add[rt]+=c;        sum[rt]+=(LL)c*(r-l+1);        return ;    }    PushDown(rt,r-l+1);    int m=(l+r)>>1;    if(L<=m) update(L,R,c,lson);    if(m<R) update(L,R,c,rson);    PushUp(rt);}LL query(int L,int R,int l,int r,int rt){    if(L<=l&&r<=R)  return sum[rt];    PushDown(rt,r-l+1);    int m=(l+r)>>1;    LL ret=0;    if(L<=m) ret+=query(L,R,lson);    if(R>m) ret+=query(L,R,rson);    return ret;}

区间覆盖 询问区间和(区间最值同理)

#include<iostream>#include<cstdio>#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1using namespace std;const int maxn=200010;typedef long long LL;LL cov[maxn<<2];//兼顾lazy标记与具体变化LL sum[maxn<<2];void PushUp(int rt){    sum[rt]=sum[rt<<1]+sum[rt<<1|1];}void PushDown(int rt,int len){    if(cov[rt]){        cov[rt<<1]=cov[rt];//下放        cov[rt<<1|1]=cov[rt];        sum[rt<<1]=cov[rt]*(len-(len>>1));        sum[rt<<1|1]=cov[rt]*(len>>1);        cov[rt]=0;//下放了,清除标记    }}void build(int l,int r,int rt){    cov[rt]=0;//初始化    if(l==r){        scanf("%lld",&sum[rt]);        return ;    }    int m=(l+r)>>1;    build(lson); build(rson);    PushUp(rt);}void update(int L,int R,int c,int l,int r,int rt){    if(L<=l&&r<=R){        cov[rt]=c;        sum[rt]=(LL)c*(r-l+1);        return ;    }    PushDown(rt,r-l+1);    int m=(l+r)>>1;    if(L<=m) update(L,R,c,lson);    if(m<R) update(L,R,c,rson);    PushUp(rt);}LL query(int L,int R,int l,int r,int rt){    if(L<=l&&r<=R)  return sum[rt];    PushDown(rt,r-l+1);    int m=(l+r)>>1;    LL ret=0;    if(L<=m) ret+=query(L,R,lson);    if(R>m) ret+=query(L,R,rson);    return ret;}

以上是一些基本操作,在实际问题中会更灵活。另外还有一些操作,如:区间交并补离散化区间合并扫描线(典型的有矩形面积并,周长并)

原创粉丝点击