线段树的那些事

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树的那些事相关的知识,希望对你有一定的参考价值。

作为一名蒟蒻,竟然学会了线段树,也是神奇。。

现在就来交流一下我对线段树的一些认识:

First:

1.线段树是一种数据结构,每个节点储存一个区间的信息

2.可以用来优化DP、求区间最值等

3.递归求解,从根节点开始往下递归

4.用空间换时间

Second:

1.主要函数步骤:

技术分享图片

这是建树函数,其中l,r表示构造的区间,lc,rc表示这个节点的左节点和右节点的编号,c为特征值(可以替换)

step1:

将当前节点的编号记录,然后确定区间范围。

step2:

判断区间大小是否为1,如果是的话就可以直接赋值了,然后return

step3:

取当前区间的mid,然后再分成左右子树开始建树。

step4:

最后更新当前节点的特征值

技术分享图片

这个是修改区间的函数:

step1:

首先判断是不是相等,如果是的话就可以直接丢tag上然后return了

step2:

接着判断左子树还是右子树要修改,左子树递归

step3:

右子树递归

技术分享图片

这个是区间求和的函数:

step1:

如果当前节点与所求区间相等,那么直接返回(记住要加上tag值)

step2:

如果当前点有tag的话,那么就把tag往下传,并且加入特征值中,然后清零

step3:

接着判断所求区间在左子树还是右子树中,然后return值;如果两个都有的话,那就加起来

2.优化

目前蒟蒻会的唯一优化是lazy tag,适用于区间修改等问题中

lazy思想:

由于每次区间修改都要递归到叶子节点,很烦,很慢,所以干脆就记录下当前节点要加多少,等到调用的时候再加上

但是要注意query求和时需要把标记往下传,并且加入特征值,清零,不然之后会重复计算

Third:

来看看一些线段树的水题:

题目描述

如题,已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数加上x

2.求出某区间每一个数的和

输入输出格式

输入格式:

第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出格式:

输出包含若干行整数,即为所有操作2的结果。

输入输出样例

输入样例:

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

输出样例:

11
8
20

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

对于100%的数据:N<=100000,M<=100000

key:

  这道题目是懒标记的模板,可以用线段树+懒标记求解

1.开始建树,特征值C表示当前节点的权值

2.对于询问1,调用change函数,递归打上懒标记(需要判断区间是否相等)

3.对于询问2,调用query函数,对l~r求和,不断递归,直到能够直接求特征值为止

my code:

#include<bits/stdc++.h>
#define maxn 100010
#define for(a,b) for(int i=a;i<=b;i++)
#define ll long long 
using namespace std;
ll len,n,Q,a[maxn],p,l,r,x;
struct node{
    ll l,r,lc,rc,c,tag,m;
}tr[maxn<<2];
void Build_Tree(ll l,ll r){
    len++;ll now=len;
    tr[now].l=l;tr[now].r=r;
    tr[now].lc=tr[now].rc=-1;
    tr[now].m=l+r>>1;
    if(l==r){tr[now].c=a[l];return;}
    ll mid=l+r>>1;
    tr[now].lc=len+1;Build_Tree(l,mid);
    tr[now].rc=len+1;Build_Tree(mid+1,r);
    tr[now].c=tr[tr[now].lc].c+tr[tr[now].rc].c;
    return;
}
void change(ll now,ll l,ll r,ll y){
    if(tr[now].l==l&&tr[now].r==r){tr[now].tag+=y;return;}
    tr[now].c+=y*(r-l+1);
    if(tr[now].m>=r) change(tr[now].lc,l,r,y);
    else if(tr[now].m<l) change(tr[now].rc,l,r,y);
    else{
        change(tr[now].lc,l,tr[now].m,y);
        change(tr[now].rc,tr[now].m+1,r,y);
    }
}
ll query(ll now,ll l,ll r){
    if(tr[now].l==l&&tr[now].r==r) return tr[now].c+tr[now].tag*(r-l+1);
    if(tr[now].tag){
        tr[tr[now].lc].tag+=tr[now].tag;
        tr[tr[now].rc].tag+=tr[now].tag;
        tr[now].c+=tr[now].tag*(tr[now].r-tr[now].l+1);
        tr[now].tag=0; 
    }
    if(tr[now].m>=r) return query(tr[now].lc,l,r);
    else if(tr[now].m<l) return query(tr[now].rc,l,r);
    else return query(tr[now].lc,l,tr[now].m)+query(tr[now].rc,tr[now].m+1,r);
}
int main(){
    scanf("%lld%lld",&n,&Q);
    for(1,n)scanf("%lld",&a[i]);
    Build_Tree(1,n);
    for(1,Q){
        scanf("%lld%lld%lld",&p,&l,&r);
        if(p==1){
            scanf("%lld",&x);
            change(1,l,r,x);
        }
        else printf("%lld\\n",query(1,l,r));
    }
    return 0; 
}








以上是关于线段树的那些事的主要内容,如果未能解决你的问题,请参考以下文章

线段树那些事

值得一做》关于双标记线段树两三事BZOJ 1798 (NORMAL-)

MySQL和B+树的那些事&mysql 索引原理

可持久化线段树(主席树)

线段树分裂合并

聊聊视频播放那些事1