线段树入门(线段懵逼树、加了一些解释,丰富了一下内容)

线段树入门(线段懵逼树)

线段树上懵逼果,线段树下我和我,线段树上找bug,掉发多又多-----------题记

在这里插入图片描述
辣鸡张当时学习的博客(如果学到了新的东西或者说有新的理解后期再更新)
xy 写得这篇也挺好的,商业 \sout{胡吹} 互吹链接???
\bullet 线段树常用来处理区间和、区间最大值、区间最小值问题,但是不仅局限于处理区间问题。
\bullet 什摸士线段树:线段树是一棵二叉搜索树。每个结点存储的是一段区间的最大值、最小值或者区间内元素的和。每个结点最多有两个孩子(可以有一个或者两个甚至是个没有孩子的单身大汉)。如果父亲结点的标号是 r o o t root ,辣么它的左孩子的标号解就是 r o o t 2 root*2 ,右孩子的标号就是 r o o t 2 + 1 root*2+1 。以下面的线段懵逼树为例:1号结点就存储的是【1,5】区间的和。
这个就是线段懵逼树
\bullet 问题引入点此打开自闭页给你一个 F u c k i n g \sout{Fucking} 序列 a 1 . . . . . . a n a_1......a_n 再来 q q 次骚操作。
每次操作有两种:“C a b c” means adding c to each of A a , A a + 1 , . . . , A b . 10000 c 10000. A_a, A_{a+1}, ... , A_b. -10000 ≤ c ≤ 10000.
“Q a b” means querying the sum of A a , A a + 1 , . . . , A b . A_a, A_{a+1}, ... , A_b. So, you need to answer each ‘Q’.
Sample Input

    10 5
    1 2 3 4 5 6 7 8 9 10
    Q 4 4
    Q 1 10
    Q 2 4
    C 3 6 3
    Q 2 4

Sample Output

    4
    55
    9
    15

懵逼少年找了许久bug,终于解决了这个题
在这里插入图片描述
\bullet 模板代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int maxn = 1e5 + 10;
LL num[maxn];
LL n,q;
struct node{LL sum,lazy;}tree[maxn*6];

void update(LL root){
    tree[root].sum=tree[root * 2].sum+tree[root * 2 + 1].sum;
    return ;
}

void build(LL root,LL l,LL r){///建树
    tree[root].lazy = 0;///初始化
    tree[root].sum = 0;///初始化
    if(l == r){///如果左端点=又端点就说明走到了叶子节点,此时的sum肯定是叶子节点的值
        tree[root].sum = num[l];
        return ;
    }
    LL m = (l + r) / 2;
    build(root*2,l,m);
    build(root*2+1,m+1,r);
    update(root);
}

void pushdown(LL root,LL l,LL r){
    if(tree[root].lazy == 0)return ;
    LL m= (l + r) / 2;
    LL left = root * 2;
    LL right = root * 2 + 1;
    tree[left].lazy += tree[root].lazy;
    tree[right].lazy += tree[root].lazy;
    tree[left].sum += tree[root].lazy * (m-l+1);
    tree[right].sum += tree[root].lazy * (r-m);
    tree[root].lazy = 0;
}


void change(LL root,LL l,LL r,LL ql,LL qr,LL val){
    if(l >= ql && r <= qr){
        tree[root].lazy += val;
        tree[root].sum += (r-l+1) * val;
        return ;
    }
    pushdown(root,l,r);
    LL m = (l + r) / 2;
    if(ql <= m)change(root*2,l,m,ql,qr,val);
    if(qr > m)change(root*2+1,m+1,r,ql,qr,val);
    update(root);
}

LL query(LL root,LL l,LL r,LL ql,LL qr){
    if(l >= ql && r <= qr){
        return tree[root].sum;
    }
    pushdown(root,l,r);
    LL m = (l + r) / 2;
    LL sum = 0;
    if(ql<=m)sum += query(root*2,l,m,ql,qr);
    if(qr>m)sum += query(root*2+1,m+1,r,ql,qr);
    return sum;
}


void input(){
   for(int i = 1;i <= n;i++)
       scanf("%lld",&num[i]);
}

int main()
{
    while(scanf("%lld%lld",&n,&q)!=EOF){
        input();
        build(1,1,n);
        while(q--){
            char str[10]={'\0'};
            scanf("%s",str);
            if(strcmp(str,"C") == 0){
                LL a,b,c;
                scanf("%lld%lld%lld",&a,&b,&c);
                change(1,1,n,a,b,c);
            }
            if(strcmp(str,"Q") == 0){
                LL a,b;
                scanf("%lld%lld",&a,&b);
                LL ans = query(1,1,n,a,b);
                printf("%lld\n",ans);
            }
        }
    }
    return 0;
}
\bullet 解释一下 b u i l d ( ) build() u p d a t e ( ) update() 函数:
void update(LL root){
    tree[root].sum=tree[root * 2].sum+tree[root * 2 + 1].sum;
    return ;
}
void build(LL root,LL l,LL r){///建树
    tree[root].lazy = 0;///初始化
    tree[root].sum = 0;///初始化
    if(l == r){///如果左端点=又端点就说明走到了叶子节点,此时的sum肯定是叶子节点的值
        tree[root].sum = num[l];
        return ;
    }
    LL m = (l + r) / 2;
    build(root*2,l,m);
    build(root*2+1,m+1,r);
    update(root);
}

这是一个递归建树的过程,下面就来模拟一下回溯的过程,现在就到了递归的出口,就是左端点=右端点,此时就走到了叶子节点,此时的sum肯定是叶子节点的值,黄色代表已经更新过值的结点,蓝色代表更新完叶子节点后回溯到上一层的结点:
在这里插入图片描述
然后就要通过 u p d a t e ( ) update() 函数更新他们的爸爸4号节点:
在这里插入图片描述更新完爸爸结点后又要回溯:
在这里插入图片描述
此时又要去更新2号节点的右儿子5号结点:
在这里插入图片描述
5号结点更新完后又回溯到了2号结点
在这里插入图片描述
然后就要通过 u p d a t e ( ) update() 函数更新2号结点:
在这里插入图片描述
之后的过程就是这样,直至整棵树构造好。

\bullet 解释一下 p u s h d o w n ( ) pushdown() c h a n g e ( ) change() 函数:

void pushdown(LL root,LL l,LL r){
    if(tree[root].lazy == 0)return ;
    LL m= (l + r) / 2;
    LL left = root * 2;
    LL right = root * 2 + 1;
    tree[left].lazy += tree[root].lazy;
    tree[right].lazy += tree[root].lazy;
    tree[left].sum += tree[root].lazy * (m-l+1);
    tree[right].sum += tree[root].lazy * (r-m);
    tree[root].lazy = 0;
}


void change(LL root,LL l,LL r,LL ql,LL qr,LL val){
    if(l >= ql && r <= qr){
        tree[root].lazy += val;
        tree[root].sum += (r-l+1) * val;
        return ;
    }
    pushdown(root,l,r);
    LL m = (l + r) / 2;
    if(ql <= m)change(root*2,l,m,ql,qr,val);
    if(qr > m)change(root*2+1,m+1,r,ql,qr,val);
    update(root);
}

如果要给一段连续的区间加上某个值 c c (发零花钱哈哈哈),当然可以每次都找到叶子节点然后将它们更新就可以了,但是这样子太费时间了(这样就叫做单点更新,但是我还是喜欢单点更新,因为代码少)。不过有更高级的做法噻:如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间)那就直接让这个结点的sum值乘以区间的长度再乘以 c c 就行了撒,那就出大问题了啊,它就十分的贪污了啊,那他的孩子结点呢,就不管了吗。为了解决这个问题,我们引入了lazy标记,就是结构体里的lazy。偷了懒之后,就把lazy的值加上它贪污的值(为甚麽要“加”,直接赋值不就好了吗,不不不,因为它有可能多次贪污哈哈)。

在加上lazy之后,为甚麽有个 p u s h d o w n ( ) pushdown() 函数呢,这个函数的作用就是将爸爸的贪污值传递给它的儿子,让他的儿子的sum加上贪污值,并且给它的儿子也标记上lazy值,然后再把自己的lazy值删去(不仅贪污,还让儿子背锅)。你可能会问这不就多此一举吗,不不不。如果题目有多次给一段区间加上某个值,假如有一次给2号结点标记了lazy值,然后下一次又要给4号结点标记lazy值,如果没有 p u s h d o w n ( ) pushdown() 函数,是不是2号结点的孩子4号就少了钱,那他的爸爸就真的贪污了。如果有 p u s h d o w n ( ) pushdown() 函数,并且 p u s h d o w n ( ) pushdown() 函数还是加在递归走左右两个孩子的语句之前,那么在给4号结点标记lazy值之前就把它的爸爸2号的lazy值传递到4号了,这样4号的钱就不会少了。这个 p u s h d o w n ( ) pushdown() 函数就是预防贪污。

注意递归走完左右两个孩子之后还要 u p d a t e ( ) update() 一下。

\bullet 解释一下 q u e r y ( ) query() 函数:
LL query(LL root,LL l,LL r,LL ql,LL qr){
    if(l >= ql && r <= qr){
        return tree[root].sum;
    }
    pushdown(root,l,r);
    LL m = (l + r) / 2;
    LL sum = 0;
    if(ql<=m)sum += query(root*2,l,m,ql,qr);
    if(qr>m)sum += query(root*2+1,m+1,r,ql,qr);
    return sum;
}

q u e r y ( ) query() 函数函数里的 p u s h d o w n ( ) pushdown() 函数跟上一个是一样的功能,就不多说了。递归出口就是如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间),就直接返回sum值就行了。

\bullet 再贴一个求区间最大值的代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int maxn = 2e5 + 10;
LL num[maxn];
LL n,q;

struct node{LL val;}tree[maxn*4];

void update(LL root){
    tree[root].val=max(tree[root*2].val,tree[root*2+1].val);
    return ;
}

void build(LL root,LL l,LL r){
    tree[root].val=-inf;
    if(l==r){
        tree[root].val=num[l];
        return ;
    }
    LL m = ( l + r ) / 2;
    build(root*2,l,m);
    build(root*2+1,m+1,r);
    update(root);
}

void change(LL root,LL l,LL r,LL pos,LL val){
    if(l == r){
        tree[root].val = val;
        return ;
    }
    int m = ( l + r ) / 2;
    if(pos <= m)change(root*2,l,m,pos,val);
    if(pos > m)change(root*2+1,m+1,r,pos,val);
    update(root);
}

int query(LL root,LL l,LL r,LL ql,LL qr){
    if(l>=ql&&r<=qr){
        return tree[root].val;
    }
    int m = (l + r) / 2;
    int ans = -inf;
    if(ql <= m)ans = max(ans,query(root*2,l,m,ql,qr));
    if(qr > m)ans = max(ans,query(root*2+1,m+1,r,ql,qr));
    return ans;
}

void input(){
    for(int i=1;i<=n;i++){
        scanf("%lld",&num[i]);
    }
}

int main()
{
    while(scanf("%lld%lld",&n,&q)!=EOF){
        input();
        build(1,1,n);
        while(q--){
            char str[10] = {'\0'};
            scanf("%s",str);
            if(strcmp(str,"Q") == 0){
                LL a,b;
                scanf("%lld%lld",&a,&b);
                LL ans=query(1,1,n,a,b);
                printf("%lld\n",ans);
            }
            if(strcmp(str,"U") == 0){
                LL a,b;
                scanf("%lld%lld",&a,&b);
                change(1,1,n,a,b);
            }
        }
    }
    return 0;
}

发布了34 篇原创文章 · 获赞 7 · 访问量 1884

猜你喜欢

转载自blog.csdn.net/qq_43628761/article/details/96898832