zoukankan      html  css  js  c++  java
  • 线段树总结

    线段树总结

                      ——这个周末训练赛和codeforces,加上自己有点偷懒导致进度严重推迟

    线段树,顾名思义是在树上的线段,通过建树来维护你需要的操作,基本的操作有:区间求和,区间求最值,区间异或(这个实际上和区间更新差不多,就是加上值这个操作换成了异或),区间覆盖,扫描线求面积,线段树求区间连续字段。

    下面从最基础的区间求最值开始:

    给你一个长度为n的序列,例如长度为5的序列{9,1,8,5,6},最大值和最小值,当然朴素算法在数据量小的时候是可以的,但是在n的数量级特别大的时候就显得略加笨重。由此想出一种办法,将数组里的数构成一棵数。如下图:

     

    这棵树中叶子保存的是每个下标的数值,两个叶子的父亲,保存的是叶子中最小的那一个,这样一直到根节点得出数列中最小值。最大值也是一样。说起来很简单,接着就是用代码实现,线段树是一个二叉树。数据量大的时候也可能是一个完全二叉树,用一个sum数组来存储每个节点的数值,然后递归建树。

    void build(int i,int l,int r)
    {
        if(l==r)
        {
            scanf("%d",&sum[i]);
            return ;
        }
        int m=(l+r)/2;
        build(i*2,l,m);
        build(i*2+1,m+1,r);
        pushup(i);//收集子节点的结果
    }
             Pushup()函数是将当前节点向下更新
             void pushup(int i)
    {
             sum[i]=min(sum[i*2],sum[i*2+1]);
    }

    当维护用途不同的时候push函数的用法是不一样的。下面每种用途的线段树push函数的写法都有讲解。

    然后是更新操作:

    void update(int id ,int val,int i,int l,int r)
    {
             if(l==r)
             {
                      sum[i]=val;//这里的操作的修改id点的值
                      return;
             }
             int m=(l+r)/2;
             if(id<=m) update(id,val,i*2,l,m);
             else update(id,val,i*2+1,m+1,r);
             pushup(i);
    }

    修改,查询操作都是从根节点开始遍历,然后当你遍历到的当前区间,在需要区间之内的时候,就可以进行你需要的操作了。

    查询操作:

    查询的时候,一个区间的最值要不就在左区间,要不就在右区间,要不然就在左加右区间(虽然很像废话,但是就是这样的)

    int query (int rt,int L,int R,int l,int r)
    {
        if(L<=l&&r<=R)
            return sum[rt];
        int m=(r+l)>>1;
        int ret=0;
        if(L<=m)
            ret=min(ret,query(rt*2,L,R,l,m)
        if(R>m)
            ret=min(ret,query(rt*2+1,L,R,m+1,r);
        return ret;
    }

    区间求和(单点更新,区间更新):        

    单点更新:

    int sum[N*4];
    void pushup(int i)
    {
             sum[i]=sum[i*2]+sum[i*2+1];
    }
    void build(int i,int l,int r)
    {
        if(l==r)
        {
            scanf("%d",&sum[i]);
            return ;
        }
     
        int m=(l+r)/2;
        build(i*2,l,m);
        build(i*2+1,m+1,r);
        pushup(i);//收集子节点的结果
    }
    /*
    在当前区间[l, r]内查询区间[ql, qr]间的目标值  
    且能执行这个函数的前提是:[l,r]与[ql,qr]的交集非空  
    其实本函数返回的结果也是 它们交集的目标值  
    */
    int query(int ql,int qr,int i,int l,int r)
    {
             if(ql<=l&&r<=qr) return sum[i];
             
             int m=(l+r)/2;
             int cur=0;
             if(ql<=m) cur+=query(ql,qr,i*2,l,m);
             if(m<qr) cur+=query(ql,qr,i*2+1,m+1,r);
             return cur;
    }
    /*
    update这个函数就有点定制的意味了  
    本题是单点更新,所以是在区间[l,r]内使得第id数的值+val  
    如果是区间更新,可以update的参数需要将id变为ql和qr  
    */
    void update(int id ,int val,int i,int l,int r)
    {
             if(l==r)
             {
                      sum[i]+=val;
                      return;
             }
             int m=(l+r)/2;
             if(id<=m) update(id,val,i*2,l,m);
             else update(id,val,i*2+1,m+1,r);
             pushup(i);
    }

    区间更新:

    这里要引进一个概念叫:延迟更新。当你想要更新一个区间的时候人进一个数组addv,addv[i]表示以i为节点的树共同增加了(addv[i]),然后在通过递归,顺带更新带叶子结点。

    const int MAXN=100000+100;
    typedef long long LL;
    #define lson i*2,l,m
    #define rson i*2+1,m+1,r
    LL sum[MAXN*4];
    LL addv[MAXN*4];
    void PushDown(int i,int num)//这就是延迟操作,更新当前结点的叶子
    {
        if(addv[i])
        {
            sum[i*2] +=addv[i]*(num-(num/2));//每个点的需要更新的值乘以的个数
            sum[i*2+1] +=addv[i]*(num/2);//同上
            addv[i*2] +=addv[i];//这个区间需要更新的个数
            addv[i*2+1]+=addv[i];
            addv[i]=0;
        }
    }
    void PushUp(int i)
    {
        sum[i]=sum[i*2]+sum[i*2+1];
    }
    void build(int i,int l,int r)
    {
        addv[i]=0;//将延迟操作更改的值需要记录到addv数组中,现在将它初始化
        if(l==r)
        {
            scanf("%I64d",&sum[i]);
            return ;
        }
        int m=(l+r)/2;
        build(lson);
        build(rson);
        PushUp(i);
    }
    void update(int ql,int qr,int add,int i,int l,int r)
    {
        if(ql<=l&&r<=qr)
        {
            addv[i]+=add;
            sum[i] += (LL)add*(r-l+1);
            return ;
        }
        PushDown(i,r-l+1);//向下更新枝叶的值
        int m=(l+r)/2;
        if(ql<=m) update(ql,qr,add,lson);
        if(m<qr) update(ql,qr,add,rson);
        PushUp(i);
    }
    LL query(int ql,int qr,int i,int l,int r)
    {
        if(ql<=l&&r<=qr)
        {
            return sum[i];
        }
        PushDown(i,r-l+1);
        int m=(l+r)/2;
        LL res=0;
        if(ql<=m) res+=query(ql,qr,lson);
        if(m<qr) res+=query(ql,qr,rson);
        return res;
    }
  • 相关阅读:
    WPF MarkupExtension
    WPF Binding小数,文本框不能输入小数点的问题
    WPF UnhandledException阻止程序奔溃
    .Net Core的总结
    C#单元测试
    Csla One or more properties are not registered for this type
    unomp 矿池运行问题随记
    矿池负载运行监测记录
    MySql 数据库移植记录
    后台服务运行后无故停止运行,原因不明
  • 原文地址:https://www.cnblogs.com/wuwangchuxin0924/p/5971849.html
Copyright © 2011-2022 走看看