浅谈线段树

线段树(长,但是好用!)

问题

给你一个长度为 \(n\)的序列\(A_1,A_2,A_3…A_n\)进行如下操作:
1.对这个序列的某个点加上一个数字vol
2.求[l,r]区间的数字和
看到这里,键盘侠就要开始喷了:你这打着线段树的名号讲树状数组的**(和谐)东西
不慌,再来看第三条:
3.求[l,r]区间的最大值和最小值
这样的话,树状数组就不好做了吧(蒟蒻得意
那么,进入正题,线段树!!!

线段树好不好用

根据某巨所言:傻逼线段树又臭又长!线段树真快真好用!

啥叫线段树?

回到上面这个问题,求区间和的操作用树状数组很容易就能维护,可是求区间最大最小……
好像树状数组就不灵了?!这个时候,线段树英勇地站了出来
首先,针对一个区间,我们可以人为地把这个区间进行划分,比如[1,5]就可以变成[1,3][4,5]两个区间合并的结果
那么,每次把一个区间进行二分,就……就得到了一棵二叉树!
[1,5]
[1,3][4,5]
[1,2][3,3][4,4][5,5]
[1,1][2,2]

void build(int p,int l,int r)
{
     if(l==r)return ;//叶结点
     int mid=(l+r) / 2;
     build(p*2, l, mid),build(p*2+1, mid+1, r);
}
//线段树建树模板

线段树怎么用?

既然我们已经有了一个个区间了,那么我们就要做点有意义的事情
Such as:开一个sum前缀和数组,mx最大值数组,mn最小值数组
可是,这些数组该怎么维护呢?

\[if(l==r)return ; \]

这一句空空如也,盘他!

if(l==r)
{
   sum[p]=a[l];
   mx[p]=a[l];
   mn[p]=a[l];
   return ;
}

这时,蒟蒻脑袋里蹦出了一个problem:树的叶结点改变,那父结点呢?
这可怎么办?
不慌,因为线段树是二叉树结构,所以我们直接提取ta儿子的信息就行了

void update(int p)
{
     sum[p]=sum[p*2]+sum[p*2+1];
     mx[p]=max(mx[p*2],mx[p*2+1]);
     mn[p]=min(mn[p*2],mn[p*2+1]);
}

单点修改

现在知道维护了以后,那么就要开始第一个操作:修改单点值
修改单点的思想其实就是搜索\(l=r=x\)的一个叶结点,但是要注意修改后的区间信息要重新丢掉update()里面去维护一下

void change(int p,int l,int r,int x,int v)
{
      if(l==r)
      {
         sum[i]+=v;
         return ;
      }
      int mid=(l+r)/2;
      if(x<=mid)change(p*2,l,mid,x,v);
      else      change(p*2+1,mid+1,r,x,v);
      update(p);
}
//单点修改前缀和模板

区间查询

看了上面这么多,有一个致命的问题:区间没有全列举到!!!
wow,这个就很难办,要是无法处理,线段树彻底成为废物
but区间是可以拆分和合并的……
还是那个[1,5]的区间,现在我们要拿出[1,4]这个区间的信息:
[1,5]
[1,3][4,5]
[1,2][3,3][4,4][5,5]
[1,1][2,2]
显然我们要用的是线段树中[1,3][4,4]两个节点,他们又都是[1,4]的一部分……
等等?![1,4]的一部分!!!(垂死病中惊坐起)

int query(int p,int l,int r,int x,int y)
//[x,y]是我们要找的区间
{
     if(l>=x&&r<=y)
        return sum[p];//调取的信息
     int mid=(l+r)/2,ans;//ans代表这个区间某个值(前缀和,最大最小值等……)
     if(x<=mid)ans+=query(p*2,l,mid,x,y);
     if(y>mid)ans+=query(p*2+1,mid+1,r,x,y);
     return ans;
}
//前缀和调取模板

------------恢复内容结束------------

posted @ 2020-06-25 21:24  蒟蒻JYX  阅读(55)  评论(2编辑  收藏  举报