浅析线段树

  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\n",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\n",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\n",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\n",qui(1,x,y)%p);
    }
  }
  return 0;
}

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

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

 

posted @ 2019-07-25 13:38  冰逝  阅读(398)  评论(0编辑  收藏  举报