• 线段树的那些事


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

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

    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
    ",query(1,l,r));
        }
        return 0; 
    }
  • 相关阅读:
    SqlMapClient对象
    斐波拉契数列的由来
    马士兵struts2
    [转]ASP.NET Repeater控件
    C# 使用委托
    C# 实现图片的放大缩小和平移
    托管改变属性的值InvokeRequired
    [转]正确使用 RamDisk Plus 的方法解决分配内存后占用系统内存的问题
    简单学习Infopath
    C# Image与ByteArray转换
  • 原文地址:https://www.cnblogs.com/heqingyu/p/8087598.html
Copyright © 2020-2023  润新知