线段树

目录

  • 数据结构--线段树
    • 一、定义
    • 二、性质
    • 三、基本操作
      • 0.结构体
      • 1.建树
      • 2.单点查询
      • 3.单点修改
      • 4.区间修改
      • 5.区间查询
      • 6.总
    • 四、题目
      • 单点修改、区间查询模板
      • 区间修改、区间查询模板
      • 区间修改、单点查询
    • 五、鸣谢
      • 学姐的Blog
      • 百度百科

数据结构--线段树

一、定义

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点$[a,b]$,它的左儿子表示的区间为$[a,(a+b)/2]$,右儿子表示的区间为$[(a+b)/2+1,b]$。因此线段树是平衡二叉树,最后的叶子节点数目为$N$,即整个线段区间的长度。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为$O(logN)$。而未优化的空间复杂度为$4N$

你们可能会问什么是区间树,我也不知道。

$如上图就是一颗[1,8]的线段树。$

二、性质

$每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]。$
$对于节点k,左孩子为k * 2,右孩子为k * 2 + 1,和二叉树一样$

三、基本操作

$线段树的基础操作主要有5个:建树、单点查询、单点修改、区间查询、区间修改。$

0.结构体

可以使用结构体也可以使用数组,看个人喜好。

struct node{
    int l,r,w;//l,r分别表示区间左右端点,w表示区间和
}tree[MAXN*4+1];//注意线段树要开四倍空间。

1.建树

void build(int l,int r,int now){
    tree[now].l=l,tree[now].r=r;//记下now这个节点所表示的区间。
    if(l==r){//now节点为叶子结点。
        scanf("%d",tree[now].w);//读入叶子结点的值。
        return;//不用进行下面的了。
    }
    int mid=(l+r)>>1;//>>1相当于/2
    build(l,mid,now<<1);//<<1相当于*2
    build(mid+1,r,now<<1|1);//<<1|1相当于*2+1
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//更新区间和
}

2.单点查询

询问第$x$个点的值。

void ask_single(int x,int now){
    if(tree[now].l==tree[now].r){//叶子结点,即最终答案。
        ans=tree[now].w;
        return;
    }
    int mid=(tree[now].l+tree[now].r)>>1;//计算区间的中点。
    if(x<=mid){
        ask_single(x,now<<1);//查找该点的左孩子
    }else{
        ask_single(x,now<<1|1);//查找该点的有孩子
    }
}

3.单点修改

给第$x$个点加上$y$。(和单点查询差不多qwq)

void updata_single(int x,int y,int now){
    if(tree[now].l==tree[now].r){
        tree[now].w+=y;
        return;
    }
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid){
        updata_single(x,y,now<<1);
    }else{
        updata_single(x,y,now<<1|1);
    }
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//单点修改后要更新区间和
}

线段树_第1张图片
修改的过程像上图一样递归修改,当修改完单点后再去更新上面的区间和。

4.区间修改

建议先看下面的区间查询。
将$[x,y]$区间每一个数加上$x$。
区间里都是点,进行$y-x+1$次单点修改。(太慢了,应该不会有人用吧。)

void update_range(int x,int y,int c){
    for(int i=x;i<=y;++i){
        update_single(i,c,1);
    }
}

上面这个做法数据不水的话就$TLE$了(在线段树1中只能拿$70$分)。
将某区间每一个数加上$x$,这个是可以加优化的。
修改需要辣么多时间,不修改不就可以了吗?
差不多,是在没用到的时候不修改。
就像是过年各种亲戚给你压岁钱,然后都到了你家长手里,$\color{red}{你用的时候再给你}$。(然后这钱可能永远也到不了你手里了,qwq)。
实现方法:
1.用一个标记记录下这个增量。
2.当要修改的区间完全包含当前区间就给当前区间的标记加上这个增量,不再向下递归。
当需要查询子节点时怎么办?
用到一个下放操作。
1.当前节点的标记累加到子节点的标记中。
2.修改子节点状态。
3.该节点标记清$0$。
结构体:

struct node{
    int w,l,r,lazy;//lazy就是这个标记。
}tree[MAXN*4+1];

下放操作:

inline void pushdown(int now){
    if(tree[now].lazy){
        tree[now<<1].lazy+=tree[now].lazy;
        tree[now<<1|1].lazy+=tree[now].lazy;//将lazy标记向下传.
        tree[now<<1].w+=(tree[now<<1].r-tree[now<<1].l+1)*tree[now].lazy;
        tree[now<<1|1].w+=(tree[now<<1|1].r-tree[now<<1|1].l+1)*tree[now].lazy;//更新区间和
        tree[now].lazy=0;//清零
    }
}

区间修改:

void update_range(int x,int y,int c,int now){
    if(tree[now].l>=x&&tree[now].r<=y){//当前区间包含于要修改的区间.
        tree[now].w+=(tree[now].r-tree[now].l+1)*c;
        tree[now].lazy+=c;
        return;
    }
    if(tree[now].lazy) pushdown(now);//下传
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_range(x,y,c,now<<1);
    if(y>mid) update_range(x,y,c,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//这几行和区间查询差不多
}

因为加入了lazy标记所以其他的操作也有改变。
单点查询:

int ask_single(int x,int now){
    if(tree[now].l==tree[now].r){
        return tree[now].w;
    }
    if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传 
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) ask_single(x,now<<1);
    else ask_single(x,now<<1|1);
}

区间查询:

void ask_range(int x,int y,int now){
    if(tree[now].l>=x&&tree[now].r<=y){
        ans+=tree[now].w;
        return;
    }
    if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传 
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) ask_range(x,y,now<<1);
    if(y>mid) ask_range(x,y,now<<1|1);
}

单点修改

void update_single(int x,int c,int now){//
    if(tree[now].l==tree[now].r){
        tree[now].w+=c;
        return;
    }
    if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传 
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_single(x,c,now<<1);
    else update_single(x,c,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}

5.区间查询

求出$[x,y]$区间每一个数的和。
在查询的过程中存在以下几种情况:
线段树_第2张图片
线段树_第3张图片

void ask_range(int x,int y,int now){
    if(tree[now].l>=x&&tree[now].r<=y){//[l,r]是[x,y]的子集
        ans+=tree[now].w;
        return;//不结束的话进行下面的会重复计算。
    }
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid){//要查询的区间在当前区间的左边
        ask_range(x,y,now<<1);
    }
    if(y>mid){//要查询的区间在当前区间的右边
        ask_range(x,y,now<<1|1);
    }
}

6.总

#include
#include
#include
#include
#include
#define MAXN 500001
using namespace std;
struct node{
    int l,r,w;
    int lazy;
}tree[MAXN<<2];
int ans;
inline int read(){
    int x=0;bool f=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=!f;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f?-x:x;
}
void build(int l,int r,int now){
    tree[now].l=l,tree[now].r=r;
    tree[now].lazy=0;
    if(tree[now].l==tree[now].r){
        tree[now].w=read();
        return;
    }
    int mid=(tree[now].l+tree[now].r)>>1;
    build(l,mid,now<<1),build(mid+1,r,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
inline void pushdown(int now){
    tree[now<<1].lazy+=tree[now].lazy;
    tree[now<<1|1].lazy+=tree[now].lazy;
    tree[now<<1].w+=tree[now].lazy*(tree[now<<1].r-tree[now<<1].l+1);
    tree[now<<1|1].w+=tree[now].lazy*(tree[now<<1|1].r-tree[now<<1|1].l+1);
    tree[now].lazy=0;
}
void update_single(int x,int k,int now){//单点修改
    if(tree[now].l==tree[now].r){
        //tree[now].w=k;将x这个位置的数改为k
        //tree[now].w+=k;将x这个位置的数加上k
        return;
    }
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_single(x,k,now<<1);
    else update_single(x,k,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
int ask_single(int x,int now){//单点查询
    if(tree[now].l==tree[now].r) return tree[now].w;
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) return ask_single(x,now<<1);
    else return ask_single(x,now<<1|1);
}
void update_range(int x,int y,int k,int now){//区间修改
    if(tree[now].l>=x&&tree[now].r<=y){
        tree[now].w+=(tree[now].r-tree[now].l+1)*k;
        tree[now].lazy+=k;
        return;
    }
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) update_range(x,y,k,now<<1);
    if(y>mid) update_range(x,y,k,now<<1|1);
    tree[now].w=tree[now<<1].w+tree[now<<1|1].w;
}
void ask_range(int x,int y,int now){
    if(tree[now].l>=x&&tree[now].r<=y){
        ans+=tree[now].w;
        return;
    }
    if(tree[now].lazy!=0) pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(x<=mid) ask_range(x,y,now<<1);
    if(y>mid) ask_range(x,y,now<<1|1);
}

int main(){
    return 0;
}

四、题目

单点修改、区间查询模板

Codevs 1080
洛谷P3374

区间修改、区间查询模板

洛谷P3372
Codevs1082

区间修改、单点查询

洛谷P3368
Codevs1081

五、鸣谢

学姐的Blog

百度百科

你可能感兴趣的:(线段树)