zoukankan      html  css  js  c++  java
  • 浅析线段树

      1.1何为线段树

      线段树是一种支持O(nlogn)的时间复杂度进行区间查询、区间修改,O(logn)进行单点修改、单点查询的数据结构,它比朴素算法(O(n2))快很多,能支持n<=1000000范围,比树状数组、ST表代码实现难度大,且常数较大,但能实现的功能多,“树状数组能做的线段树都能做,线段树能做的树状数组不一定能做”

      1.2原理概述

      既然叫线段树,那肯定是棵树,准确的说,是一棵二叉树,记u为节点编号,l[u]为区间左边界,r[u]为区间右边界,若该店非叶子节点,则记mid=(l[u+r[u])/2,u的左孩子编号为u*2,表示区间为(l[u],mid),u的右孩子编号为u*2+1,表示区间为(mid+1,l[u]);

      考虑n=8的情况:

      其中,圆圈内的点为节点的编号,而括号内则表示该节点所表示的区间。加上数值后,父亲节点的值为两儿子的和。

      2.1代码实现

      在前面提到,树状数组能做的线段树一定能做,我们先看一道模板题:【模板】树状数组 1

      首先,对于区间有n个点的话,线段树要将数组开到4*n,这个一定要记得。

      该题的要求是单点修改和区间求和,首先是建树:

     void build(int u,int l1,int r1)
    {
        l[u]=l1;
        r[u]=r1;//区间的左、右端点;
        if (l1==r1)
        {
            z[u]=a[l1];//因为l1==r1==该点的坐标,将该点的值赋给z[u]
            return;
        }
         build(u*2,l1,(l1+r1)/2);
         build(u*2+1,(l1+r1)/2+1,r1);//若非叶节点,继续建树
         z[u]=z[u*2]+z[u*2+1];//记录区间值
     }

      l[u]、r[u]的含义如上述,z[u]表示该区间的和;

      其次,是实现单点修改的程序:

      

    void jia(int u,int x,int k)
    {
      if (l[u]>x||r[u]<x) return;//若x在该区间之外,直接返回 if (l[u]==r[u])//因为该区间包含x,若该区间是个点则这个点就是x { z[u]+=k; return; } jia(u*2,x,k); jia(u*2+1,x,k); z[u]=z[u*2]+z[u*2+1];//更新z[u] }

      之后是区间求和:

    int qui(int u,int l1,int r1)
    {
        if (l[u]>r1||r[u]<l1) return 0;//若该区间不在所求区间内,返回0
        else if (l[u]>=l1&&r[u]<=r1) return z[u];//若该区间被所求区间包含,返回该区间的值
        else return qui(u*2,l1,r1)+qui(u*2+1,l1,r1);//继续向下找
    }

      完整代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,i,a[500001],z[2000001],l[2000001],r[2000001],q,x,y;
    void build(int u,int l1,int r1)
    {
        l[u]=l1;
        r[u]=r1;
        if (l1==r1)
        {
            z[u]=a[l1];
            return;
        }
        build(u*2,l1,(l1+r1)/2);
        build(u*2+1,(l1+r1)/2+1,r1);
        z[u]=z[u*2]+z[u*2+1];
    }
    void jia(int u,int x,int k)
    {
        if (l[u]>x||r[u]<x) return;
        if (l[u]==r[u])
        {
            z[u]+=k;
            return;
        }
        jia(u*2,x,k);
        jia(u*2+1,x,k);
        z[u]=z[u*2]+z[u*2+1];
    }
    int qui(int u,int l1,int r1)
    {
        if (l[u]>r1||r[u]<l1) return 0;
        else if (l[u]>=l1&&r[u]<=r1) return z[u];
        else return qui(u*2,l1,r1)+qui(u*2+1,l1,r1);
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for (i=1;i<=n;i++)
            scanf("%d",&a[i]);
        build(1,1,n);
        for (i=1;i<=m;i++)
        {
            scanf("%d%d%d",&q,&x,&y);
            if (q==1)
                jia(1,x,y);
            else
                printf("%d
    ",qui(1,x,y));
        }
        return 0;
    }

      2.2标记下传

      接下来,我们看一道区间修改的题:【模板】树状数组 2

      有人觉得区间修改就是对该区间每个点进行单点修改,但仔细算算,它的时间复杂度还不如朴素算法快呢,这就用得到线段树的另一个特点:懒标记

      当我们发现有一区间在所求区间之内时,先不要对该区间下的节点进行修改,而是对其修改后进行标记,若需要求该区间下的值时再下传

      实现下传代码如下:

    void xiafang(int u)//下放
    {
      z[u*2]+=c[u]*(r[u*2]-l[u*2]+1);//c[u]即为该区间内每点应该加上的值,则区间加上该区间点的个数乘上c[u]
      c[u*2]+=c[u];
      z[u*2+1]+=c[u]*(r[u*2+1]-l[u*2+1]+1);
      c[u*2+1]+=c[u];
      c[u]=0;//记得清零
    }

      可能有些看不懂吧?那么,全部代码奉上:

    #include <bits/stdc++.h>
    using namespace std;
    int n,m,i,q,x,y,a,j,l[50000001],r[50000001],z[50000001],w[50000001],c[50000001],k,x1[5000001];//至少4倍
    void xiafang(int u)
    {
      z[u*2]+=c[u]*(r[u*2]-l[u*2]+1);
      c[u*2]+=c[u];
      z[u*2+1]+=c[u]*(r[u*2+1]-l[u*2+1]+1);
      c[u*2+1]+=c[u];
      c[u]=0;
    }
    void build(int u,int l1,int r1)
    {
      l[u]=l1;
      r[u]=r1;
      if (l1==r1)
      {
        z[u]=x1[l1];
        return;
      }
      build(u*2,l1,(l1+r1)/2);
      build(u*2+1,(l1+r1)/2+1,r1);
      z[u]=z[u*2]+z[u*2+1];
    }
    void jia(int u,int l1,int r1,int k)//这是与之前不同的地方
    {
      if ((l[u]>r1)||(r[u]<l1)) return;
      if ((l[u]>=l1)&&(r[u]<=r1))
      {
        z[u]+=k*(r[u]-l[u]+1);//修改z[u]
        c[u]+=k;//c[u]懒标记
        return;
      }
      xiafang(u);//下传标记
      jia(u*2,l1,r1,k);
      jia(u*2+1,l1,r1,k);
      z[u]=z[u*2]+z[u*2+1];
    }
    int qui(int u,int x)
    {
      if ((x>r[u])||(x<l[u])) return 0;
      else if (l[u]==r[u]) return z[u];
      else
      {
        xiafang(u);//下传标记
        return (qui(u*2,x)+qui(u*2+1,x));
      }
    }
    int main()
    {
      scanf("%d%d",&n,&m);
      for (i=1; i<=n; i++)
        scanf("%d",&x1[i]);
      build(1,1,n);
      for (i=1; i<=m; i++)
      {
        scanf("%d%d",&q,&x);
        if (q==1)
        {
          scanf("%d%d",&y,&k);
          jia(1,x,y,k);
        }
        else printf("%d
    ",qui(1,x));
      }
      return 0;
    }

      3.综合运用

      看这两道题【模板】线段树 1【模板】线段树 2

      希望读者能靠自己将这两道题A掉

      这是我的AC代码:

      P3372 【模板】线段树 1

    #include <bits/stdc++.h>
    using namespace std;
    long long n,m,i,q,x,y,a,j,l[5000001],r[5000001],z[5000001],t[500001],w[5000001],c[5000001],k,x1[100001];
    void xiafang(long long u)
    {
      z[u*2]+=c[u]*(r[u*2]-l[u*2]+1);
      c[u*2]+=c[u];
      z[u*2+1]+=c[u]*(r[u*2+1]-l[u*2+1]+1);
      c[u*2+1]+=c[u];
      c[u]=0;
    }
    void build(long long u,long long l1,long long r1)
    {
      l[u]=l1;
      r[u]=r1;
      if (l1==r1)
      {
        z[u]=x1[l1];
        return;
      }
      build(u*2,l1,(l1+r1)/2);
      build(u*2+1,(l1+r1)/2+1,r1);
      z[u]=z[u*2]+z[u*2+1];
    }
    void jia(long long u,long long l1,long long r1,long long k)
    {
      if ((l[u]>r1)||(r[u]<l1)) return;
      if ((l[u]>=l1)&&(r[u]<=r1))
      {
        z[u]+=k*(r[u]-l[u]+1);
        c[u]+=k;
        return;
      }
      xiafang(u);
      jia(u*2,l1,r1,k);
      jia(u*2+1,l1,r1,k);
      z[u]=z[u*2]+z[u*2+1];
    }
    long long qui(long long u,long long l1,long long r1)
    {
      if ((l1>r[u])||(r1<l[u])) return 0;
      else if ((l1<=l[u])&&(r1>=r[u])) return z[u];
      else
      {
        if (c[u]>0) xiafang(u);
        return (qui(u*2,l1,r1)+qui(u*2+1,l1,r1));
      }
    }
    int main()
    {
      scanf("%lld%lld",&n,&m);
      for (i=1; i<=n; i++)
        scanf("%lld",&x1[i]);
      build(1,1,n);
      for (i=1; i<=m; i++)
      {
        scanf("%lld%lld%lld",&q,&x,&y);
        if (q==1)
        {
          scanf("%lld",&k);
          jia(1,x,y,k);
        }
        else printf("%lld
    ",qui(1,x,y));
      }
      return 0;
    }

      P3373 【模板】线段树 2

    #include <bits/stdc++.h>
    using namespace std;
    unsigned long long z[5000001],l[5000001],r[5000001],c[5000001],s[5000001],n,m,p,i,t[500001],x,y,k,q;
    void xiafang(unsigned long long u)
    {
      z[u*2]=(z[u*2]*s[u]+c[u]*(r[u*2]-l[u*2]+1))%p;
      c[u*2]=(c[u*2]*s[u]+c[u])%p;
      s[u*2]=s[u*2]*s[u]%p;
      c[u*2+1]=(c[u*2+1]*s[u]+c[u])%p;
      s[u*2+1]=s[u*2+1]*s[u]%p;
      z[u*2+1]=(z[u*2+1]*s[u]+c[u]*(r[u*2+1]-l[u*2+1]+1))%p;
      c[u]=0;
      s[u]=1;
    }
    void build(unsigned long long u,unsigned long long l1,unsigned long long r1)
    {
      l[u]=l1;
      r[u]=r1;
      s[u]=1;
      if (l1==r1)
      {
        z[u]=t[l1]%p;
        return;
      }
      build(u*2,l1,(l1+r1)/2);
      build(u*2+1,(l1+r1)/2+1,r1);
      z[u]=(z[u*2]+z[u*2+1])%p;
    }
    void cheng(unsigned long long u,unsigned long long l1,unsigned long long r1,unsigned long long k)
    {
      if ((r[u]<l1)||(l[u]>r1)) return;
      else if ((r1>=r[u])&&(l[u]>=l1))
      {
        z[u]=z[u]*k%p;
        c[u]=c[u]*k%p;
        s[u]=s[u]*k%p;
        return;
      }
      else
      {
        xiafang(u);
        cheng(u*2,l1,r1,k);
        cheng(u*2+1,l1,r1,k);
        z[u]=(z[u*2]+z[u*2+1])%p;
      }
    }
    void jia(unsigned long long u,unsigned long long l1,unsigned long long r1,unsigned long long k)
    {
      if ((r[u]<l1)||(l[u]>r1)) return;
      else if ((r1>=r[u])&&(l[u]>=l1))
      {
        z[u]=(z[u]+k*(r[u]-l[u]+1))%p;
        c[u]=(c[u]+k)%p;
        return;
      }
      else
      {
        xiafang(u);
        jia(u*2,l1,r1,k);
        jia(u*2+1,l1,r1,k);
        z[u]=(z[u*2]+z[u*2+1])%p;
      }
    }
    unsigned long long qui(unsigned long long u,unsigned long long l1,unsigned long long r1)
    {
      if ((l[u]>r1)||(r[u]<l1)) return 0;
      else if ((l[u]>=l1)&&(r[u]<=r1)) return z[u];
      else
      {
        xiafang(u);
        return (qui(u*2,l1,r1)+qui(u*2+1,l1,r1))%p;
      }
    }
    int main()
    {
      scanf("%lld%lld%lld",&n,&m,&p);
      for (i=1; i<=n; i++)
        scanf("%lld",&t[i]);
      build(1,1,n);
      for (i=1; i<=m; i++)
      {
        scanf("%lld",&q);
        if (q==1)
        {
          scanf("%lld%lld%lld",&x,&y,&k);
          cheng(1,x,y,k);
        }
        else if (q==2)
        {
          scanf("%lld%lld%lld",&x,&y,&k);
          jia(1,x,y,k);
        }
        else
        {
          scanf("%lld%lld",&x,&y);
          printf("%lld
    ",qui(1,x,y)%p);
        }
      }
      return 0;
    }

      如果读者是T掉或有些WA掉应该是没用高效读入、输出和没开long long吧

      可以在我的其他博文中看一下可持久化数组(可持久化线段树/平衡树)解析

  • 相关阅读:
    HDU_1285_拓扑排序(优先队列)
    HDU_1087_Super Jumping! Jumping! Jumping!_dp
    STL_优先队列_(转载)
    数据结构课程笔记_拓扑排序
    滋阴清火中药方 (推荐--自用)
    文件与文档内容搜索工具软件
    CrossUI SPA Builder ---- feathers API框架
    广州交警网上车管所
    BIM 相关资料
    WIN10 ISO 官方
  • 原文地址:https://www.cnblogs.com/szbszb/p/11243728.html
Copyright © 2011-2022 走看看