普通线段树——高级数据结构入门

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/79285051

一.线段树引入.

线段树,是一种基于分治思想的高级树形数据结构,可以支持满足快速合并性质的信息维护.

线段树作为较为容易也较传统的高级数据结构,应用广泛,在高级数据结构中占有重要的地位.

二.线段树概述.

我们先来回忆一下分治,分治的一个应用就是想要得到一个区间的值f(l,r),这个值不能直接求得,但是可以借助f(l,mid)f(mid+1,r)来快速得到,而且对于任意一个x,f(x,x)的值也可以快速得到,那么就可以通过分治来求得.

一个经典的算法就是归并排序,它要排序区间[1,n],那么就先排好区间[1,mid][mid+1,n],合并区间[1,mid][mid+1,n]已经排好的两个序列.然后[1,mid]也不能直接排好,就把这个区间也通过两个子区间的信息合并...直到区间变成了[x,x],这个区间直接有序了,就可以直接返回求得排好序的序列.

那么我们把上述分治的过程画成一张图(假设是区间[1,8])的话:

我们发现这其实是一棵树,而且我们可以把这个东西显式建树,就可以得到一棵线段树.

三.基本操作Merge.

既然一个区间[l,r]的信息要通过区间[l,mid][mid,r]的信息合并来得到,那么自然要写一个函数来实现这个功能了.

虽然一般来说,这个函数的代码量通常很小,都不会单独写一个函数,但是在这里还是写一下吧,这里以维护区间和为例:

tree Merge(tree a,tree b){
  tree c;
  c.l=a.l;c.r=b.r;
  c.sum=c.sum+b.sum;
  return c;
}

四.建树Build.

建树的过程其实就是一个分治,然后把每一个节点的信息存下来.

这里我们依然以维护区间和为例,设a数组为初始的值的情况,那么就分治到叶子(区间[l,r]满足l=r),就把信息直接存到叶子上,其它节点的信息就通过两个儿子的信息合并就可以了.

由于我们的分治就是将所有点操作了一遍,而这棵完全二叉树的节点个数为2n-1,所以建树是O(n)的.

具体代码如下:

void Build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].sum=a[L];
    return;
  }
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
  tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}

五.单点操作.

我们考虑一个单点操作,比如说单点查询一个点x的值.这个应该比较容易,可以从根开始不断往下递归找到叶子节点[x,x],然后直接返回这个叶子的信息即可.

我们在考虑一个单点修改的操作,比如说给一个点x加上v,那么我们可以找到叶子[x,x],讲叶子的信息直接加上v.但是叶子发生改变后,会使该叶子的所有祖先的信息发生改变,所以我们要在回溯的时候,再让经过的节点进行merge操作.

由于每次操作最多只会经过一条从根到叶子的链,长度为树高,而树高为O(logn),所以单点操作的时间复杂度为O(logn).

具体代码如下:

int Query(int x,int k=1){
  if (tr[k].l==tr[k].r) return tr[k].sum;
  int mid=tr[k].l+tr[k].r>>1;
  if (x<=mid) return Query(x,k<<1);
  else return Query(x,k<<1|1);
}

void Add(int x,int num,int k=1){
  if (tr[k].l==tr[k].r){
    tr[k].sum+=num;
    return;
  }
  int mid=tr[k].l+tr[k].r>>1;
  if (x<=mid) Add(x,num,k<<1);
  else Add(x,num,k<<1|1);
  tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}

六.区间查询操作.

对于区间查询[l,r]信息并的操作,我们当然可以利用上面单点查询每一个点,然后将信息并起来,但是这样就会使时间复杂度变为O(nlogn),十分不优美.

我们考虑从根节点向下的时候,若要查询的区间包括在一个节点的左儿子,直接往左儿子向下;右儿子同理;若左右儿子都有,则左右儿子都递归进入,最后将两个儿子中找到的答案合并起来就行了.

但是这样的时间复杂度貌似还是O(n),但其实我们可以证明这是O(logn)的.

证明如下:

首先,这个区间[l,r]在每一层,都只会遍历到最多4个节点.

若会遍历到4个以上节点,则必定有两个节点表示的区间是直接被[l,r]包含且是兄弟的关系,那么这两个节点应该会在上一层就被遍历过了,不会在这一层再被遍历了.

所以,每一层最多会遍历到4个节点,所以时间复杂度为O(logn).

证毕.

那么区间查询代码如下(以区间加为例):

int Query(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return Query(L,R,k<<1);
  else if (L>mid) return Query(L,R,k<<1|1);
    else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1); 
}

七.区间修改与lazy-tag.

区间修改,我们以给区间[l,r]的每个数加上y为例.

我们考虑与区间查询类似,直接找到每一个需要修改的修改,然后对每一个区间直接加上y*区间长度.

但是这样子的话,被修改的节点下所有节点都要修改,但这样做就会无法修改,暴力修改又太慢,我们该怎么办?

我们引入lazy-tag的概念,给每一个修改的节点在打上一个tag标记,表示这个节点下面的所有节点都要增加一个值.

那么,当我们要查询点的时候,对所有经过的节点的tag往下推,对下面的节点进行修改,我们把这个过程写成一个函数pushdown.

那么我们就可以做到O(logn)修改啦,但是要注意若有tag,最好在任何操作经过任何节点都pushdown.

代码如下:

void Update_add(int k,int num){      //为了简洁写一个Update函数 
  tr[k].sum+=num*(tr[k].r-tr[k].l+1);
  tr[k].tag+=num; 
}

void Pushdown(int k){
  Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
  tr[k].tag=0;
}

void Add(int L,int R,int num,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
    Update_add(k,num);
    return;
  }
  Pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) Add(L,R,num,k<<1);
  else if (L>mid) Add(L,R,num,k<<1|1);
    else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1);
  tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}

八.例题与代码.

现在我们拿出三道例题,分别为luogu3372luogu3374luogu3368.

luogu3372代码:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000;

int n,m;
LL a[N+9];
struct tree{
  int l,r;
  LL sum,tag;
}tr[N*4+9];

void Update_add(int k,LL num){
  tr[k].sum+=num*(tr[k].r-tr[k].l+1);
  tr[k].tag+=num;
}

void Pushdown(int k){
  Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
  tr[k].tag=0LL; 
}

void Build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].sum=a[L];
    return;
  }
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}

void Add(int L,int R,LL num,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
    Update_add(k,num);
    return;
  }
  Pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) Add(L,R,num,k<<1);
  else if (L>mid) Add(L,R,num,k<<1|1);
    else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}

LL Query(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
  Pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return Query(L,R,k<<1);
  else if (L>mid) return Query(L,R,k<<1|1);
    else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1); 
}

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%lld",&a[i]);
}

Abigail work(){
  Build(1,n);
}

Abigail getans(){
  int opt,l,r;
  LL x;
  for (int i=1;i<=m;++i){
    scanf("%d",&opt);
    if (opt==1){
      scanf("%d%d%lld",&l,&r,&x);
      Add(l,r,x);
    }else{
      scanf("%d%d",&l,&r);
      printf("%lld\n",Query(l,r));
    }
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
} 

luogu3374代码:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000;

int n,m;
LL a[N+9];
struct tree{
  int l,r;
  LL sum;
}tr[N*4+9];

void Build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].sum=a[L];
    return;
  }
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}

void Add(int x,LL num,int k=1){
  if (tr[k].l==tr[k].r){
    tr[k].sum+=num;
    return;
  }
  int mid=tr[k].l+tr[k].r>>1;
  if (x<=mid) Add(x,num,k<<1);
  else Add(x,num,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum; 
}

LL Query(int L,int R,int k=1){
  if (tr[k].l==L&&tr[k].r==R) return tr[k].sum;
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return Query(L,R,k<<1);
  else if (L>mid) return Query(L,R,k<<1|1);
    else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%lld",&a[i]);
}

Abigail work(){
  Build(1,n);
}

Abigail getans(){
  int opt,l,r;
  LL x;
  for (int i=1;i<=m;++i){
    scanf("%d",&opt);
    if (opt==1){
      scanf("%d%lld",&l,&x);
      Add(l,x);
    }else{
      scanf("%d%d",&l,&r);
      printf("%lld\n",Query(l,r));
    }
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
} 

luogu3368代码:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000;

int n,m;
LL a[N+9];
struct tree{
  int l,r;
  LL sum,tag;
}tr[N*4+9];

void Update_add(int k,LL num){
  tr[k].sum+=num*(tr[k].r-tr[k].l+1);
  tr[k].tag+=num;
}

void Pushdown(int k){
  Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
  tr[k].tag=0;
}

void Build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].sum=a[L];
    return;
  }
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}

void Add(int L,int R,LL num,int k=1){
  if (tr[k].l==L&&tr[k].r==R){
    Update_add(k,num);
    return;
  }
  Pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) Add(L,R,num,k<<1);
  else if (L>mid) Add(L,R,num,k<<1|1);
    else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1|1);
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}

LL Query(int x,int k=1){
  if (tr[k].l==tr[k].r) return tr[k].sum;
  Pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (x<=mid) return Query(x,k<<1);
  else return Query(x,k<<1|1);
}

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%lld",&a[i]);
}

Abigail work(){
  Build(1,n);
}

Abigail getans(){
  int opt,l,r;
  LL x;
  for (int i=1;i<=m;++i){
    scanf("%d",&opt);
    if (opt==1){
      scanf("%d%d%lld",&l,&r,&x);
      Add(l,r,x);
    }else{
      scanf("%d",&l);
      printf("%lld\n",Query(l));
    }
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
} 

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/79285051