线段树入门到入门到入门

好几天没写博客了,然后做线段bug树做不动,回来写个博客冷静一下hhhhh——萌新的成长之路

主要说线段树的创建,单点、区间的修改、查询,lazy标记和在修改和查询时候的下传标记

一、线段树是什么?

  • 百科说:线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。区间操作的时间复杂度是O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。为什么线段树要开4倍空间
  • 功能:单点、区间的修改、查询
  • 时间复杂度:更新—O(logn);查询—O(logn);建树—o(nlogn)
  • 线段树和其他RMQ算法的区别

      常用的解决RMQ问题有ST算法,二者预处理时间都是O(NlogN),而且ST算法的单次查询操作是O(1),看起来比线段树好多了,但二者的区别在于线段树支持在线更新值,而ST算法不支持在线操作。
  这里也存在一个误区,刚学线段树的时候就以为线段树和树状数组差不多,用来处理RMQ问题和求和问题,但其实线段树的功能远远不止这些,我们要熟练的理解线段这个概念才能更加深层次的理解线段树。

二、线段树的基本操作

\color{red}{下面的内容都是以维护区间和为例介绍它的一些最基本的操作}
例题:poj 3468: A Simple Problem with Integers
题目大意:给出n个数,和m个操作,每个操作修改一段连续区间[a,b]的值或询问区间[l,r]的和,并输出

先看它是怎么划分区间的:(线段树是建立在线段的基础上,每个结点都代表了一条线段[a,b]。它的两个子结点中:左结点代表的线段为[a,(a + b) / 2],右结点代表的线段为[((a + b) / 2)+1,b])
在这里插入图片描述
在维护区间和的问题中,原区间[1,n]的值都是存在叶子节点的

1、线段树的创建

如上图,线段树中的每个结点都表示一段区间的和,先给子节点赋值,在回溯的过程中得到各区间段的和
若一个结点的下标为root, 则他的左儿子下标为 root << 1, 右儿子下标为( rt << 1) | 1.

LL num[manx];//存的是原来区间[1,n]的值,必须从1开始存
struct node
{
    LL sum,lazy;
}tree[manx<<2];
void eval(int root)
{
    tree[root].sum=tree[root<<1].sum+tree[(root<<1)|1].sum;//可以看图,比如[1,3]的和就是它的左右儿子[1,2]和[3,3]的和
}
void build_tree(int root,int l,int r)
{
    tree[root].lazy=0;//初始化,如果是乘法就初始化为1,这个是懒惰标记的操作
    if(l==r)//遍历到叶子结点,赋值,然后返回
    {
        tree[root].sum=num[l];
        return;
    }
    int mid=(l+r)>>1;
    build_tree(root<<1,l,mid);
    build_tree((root<<1)|1,mid+1,r);
    eval(root);//得到左右儿子的值之后,合并得到根节点的值
}
build_tree(1,1,n);

2、单点的修改

看一组数据:

6 3
5 2 8 4 5 6
C 1 3 2
C 1 2 1
Q 1 3

构造的线段树如下:
在这里插入图片描述
如果要修改第二个数的值,那么往上看,影响的结点——就是遍历到点[2,2]这条路径上的点,如图:
在这里插入图片描述

void change(int root,int l,int r,int pos,LL val)
{
    if(l==r)//便利到要改的这个结点[pos,pos]
    {
        tree[root].sum=val;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)//二分查找
     change(root<<1,l,mid,pos,val);
    else
     change((root<<1)|1,mid+1,r,pos,val);
    eval(root);
}
change(1,1,n,pos,val);

单点的查询和区间查询一样不过区间查询多了一个操作

3、区间修改

商业互吹时刻(「・ω・)「嘿:
如果要给一段连续的区间加上某个值 c c (发零花钱哈哈哈),当然可以每次都找到叶子节点然后将它们更新就可以了,但是这样子太费时间了(这样就叫做单点更新,但是我还是喜欢单点更新,因为代码少)。不过有更高级的做法噻:如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间)那就直接让这个结点的sum值乘以区间的长度再乘以 c c 就行了撒,那就出大问题了啊,它就十分的贪污了啊,那他的孩子结点呢,就不管了吗。为了解决这个问题,我们引入了lazy标记,就是结构体里的lazy。偷了懒之后,就把lazy的值加上它贪污的值(为甚麽要“加”,直接赋值不就好了吗,不不不,因为它有可能多次贪污哈哈)。
在加上lazy之后,为甚麽有个 p u s h d o w n ( ) pushdown() 函数呢,这个函数的作用就是将爸爸的贪污值传递给它的儿子,让他的儿子的sum加上贪污值,并且给它的儿子也标记上lazy值,然后再把自己的lazy值删去(不仅贪污,还让儿子背锅)。线段树入门(线段懵逼树
回到这组数据:

6 3
5 2 8 4 5 6
C 1 3 2
C 1 2 1
Q 1 3

第一次操作之后:
在这里插入图片描述
继续看为什么还要用pushdown()函数将标记下移,不是已经将区间和改变了吗?
1、对查询的影响:如果现在要找[1,2]的区间和,因为前面加了2,所以他们的和应该是11,虽然我们修改了代表[1,3]区间和的结点值,但下面没有改变,所以图中还是7

C 1 2 1

2、对下一次操作的影响:因为在修改之后都要进行回溯操作,下图是修改返回的时候还没有回溯修改上面的值,也没有pushdown()操作
在这里插入图片描述
可以看到,如果从这里回溯的话,[1,3]的区间和就变成了17,但是前面经过两次修改,[1,3]的区间和应该是23
因为除叶子结点的区间和都是我们在回溯的过程中,通过两个孩子结点的值和并得来的,所以要知道父亲贪污了多少钱,还是要先让儿子知道之前和这次总共贪污了多少钱,如果没有用pushdown()告诉它的儿子自己之前贪污的钱,那儿子也不知道,下一次贪污的时候父亲也忘了,就少算了
所以正确的操作结果应该是这样子得:(先告诉他的儿子,再进行下一次操作)
在这里插入图片描述
终于可以贴代码了:

void pushdown(int root,LL l,LL r)
{
     if(tree[root].lazy==0)
          return;
     LL mid=(l+r)>>1;
     int chl=root<<1;
     int chr=(root<<1)+1;
     tree[chl].sum+=(mid-l+1)*tree[root].lazy;
     tree[chr].sum+=(r-mid)*tree[root].lazy;
     tree[chl].lazy+=tree[root].lazy;
     tree[chr].lazy+=tree[root].lazy;
     tree[root].lazy=0;///记得置0
}
void changelong(int root,LL l,LL r,int ll,int rr,LL val)
{
    if(l==ll&&rr==r)//找到区间
    {
        tree[root].sum+=val*(r-l+1);
        tree[root].lazy+=val;
        return;
    }
    pushdown(root,l,r);///(主角)
    int mid=(l+r)>>1;
    if(mid>=rr)//二分,如果整个区间都在左边的话,就不用遍历右边区间了
     changelong(root<<1,l,mid,ll,rr,val);
    else if(mid<ll)
     changelong((root<<1)|1,mid+1,r,ll,rr,val);
    else
    {//两边都有的话就分开改,比如说要改[1,5]的
         changelong(root<<1,l,mid,ll,mid,val);
         changelong((root<<1)|1,mid+1,r,mid+1,rr,val);
    }
    eval(root);//合并
}

3、询问

(怎么还有一个询问……)
嗯,,,,直接找就行了,记得加pushdown(),防止询问儿子结点或儿子的儿子的时候,父亲还没有告诉它,就出错了

LL query(int root,int l,int r,int ll,int rr)
{
    if(l==ll&&r==rr)
    {
        return tree[root].sum;
    }
    pushdown(root,l,r);//单点查询可以不用加pushdown()
    int mid=(l+r)>>1;
    if(mid>=rr)
        return query(root<<1,l,mid,ll,rr);
    else if(mid<ll)
        return query((root<<1)|1,mid+1,r,ll,rr);
    else
        return query(root<<1,l,mid,ll,mid)+query((root<<1)|1,mid+1,r,mid+1,rr);
}

整体实现:

#include <iostream>
#include <string.h>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
LL num[manx];
struct node
{
    LL sum,lazy;
}tree[manx<<2];
void eval(int root)
{
    tree[root].sum=tree[root<<1].sum+tree[(root<<1)|1].sum;
}
void build_tree(int root,int l,int r)
{
    tree[root].lazy=0;
    tree[root].sum=0;
    if(l==r)
    {
        tree[root].sum=num[l];
        return;
    }
    int mid=(l+r)>>1;
    build_tree(root<<1,l,mid);
    build_tree((root<<1)|1,mid+1,r);
    eval(root);
}
void pushdown(int root,LL l,LL r)
{
     if(tree[root].lazy==0)
          return;
     LL mid=(l+r)>>1;
     int chl=root<<1;
     int chr=(root<<1)+1;
     tree[chl].sum+=(mid-l+1)*tree[root].lazy;
     tree[chr].sum+=(r-mid)*tree[root].lazy;
     tree[chl].lazy+=tree[root].lazy;
     tree[chr].lazy+=tree[root].lazy;
     tree[root].lazy=0;
}
void changelong(int root,LL l,LL r,int ll,int rr,LL val)
{
    if(l==ll&&rr==r)
    {
        tree[root].sum+=val*(r-l+1);
        tree[root].lazy+=val;
        return;
    }
    pushdown(root,l,r);
    int mid=(l+r)>>1;
    if(mid>=rr)
     changelong(root<<1,l,mid,ll,rr,val);
    else if(mid<ll)
     changelong((root<<1)|1,mid+1,r,ll,rr,val);
    else
    {
         changelong(root<<1,l,mid,ll,mid,val);
         changelong((root<<1)|1,mid+1,r,mid+1,rr,val);
    }
    eval(root);
}
LL query(int root,int l,int r,int ll,int rr)
{
    if(l==ll&&r==rr)
    {
        return tree[root].sum;
    }
    pushdown(root,l,r);
    int mid=(l+r)>>1;
    if(mid>=rr)
        return query(root<<1,l,mid,ll,rr);
    else if(mid<ll)
        return query((root<<1)|1,mid+1,r,ll,rr);
    else
        return query(root<<1,l,mid,ll,mid)+query((root<<1)|1,mid+1,r,mid+1,rr);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        scanf("%lld",&num[i]);
    build_tree(1,1,n);
    char opert[30];
    while(m--)
    {
        scanf("%s",opert);
        if(opert[0]=='C')
        {
            int l,r;
            LL val;
            scanf("%d%d%lld",&l,&r,&val);
            changelong(1,1,n,l,r,val);
        }
        else
        {
            int l,r;
            scanf("%d%d",&l,&r);
            printf("%lld\n",query(1,1,n,l,r));
        }
    }
}

例题:
维护区间最大值:http://acm.hdu.edu.cn/showproblem.php?pid=1754
维护区间和(加+乘):https://www.luogu.org/problem/P2023

后面可以不用看了


最开始一直在想既然lazy记录了所有修改过的情况,那就可以在询问的时候下传个标记就行了啊,为啥还要在修改里面加一个,然后,然后我就把主角删了,,T_T,开始修改里面也没有写合并,一直错,还不知道为啥T_T,,,,,
但是改过来之后还是把主角去了,又写了一个,不过合并哪里改了一下

#include <iostream>
#include <string.h>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
LL num[manx];
struct node
{
    LL sum,lazy;
}tree[manx<<3];
void eval(int root,int l,int r)
{//既然没告诉儿子,就自己加上去hhh,还是要自己记
    tree[root].sum=tree[root<<1].sum+tree[(root<<1)|1].sum+tree[root].lazy*(r-l+1);
}
void build_tree(int root,int l,int r)
{
    tree[root].lazy=0;
    tree[root].sum=0;
    if(l==r)
    {
        tree[root].sum=num[l];
        return;
    }
    int mid=(l+r)>>1;
    build_tree(root<<1,l,mid);
    build_tree((root<<1)|1,mid+1,r);
    tree[root].sum=tree[root<<1].sum+tree[(root<<1)|1].sum;
}
void pushdown(int root,int l,int r)
{
     if(tree[root].lazy==0)
          return;
     int mid=(l+r)>>1;
     int chl=root<<1;
     int chr=(root<<1)+1;
     tree[chl].sum+=(mid-l+1)*tree[root].lazy;
     tree[chr].sum+=(r-mid)*tree[root].lazy;
     tree[chl].lazy+=tree[root].lazy;
     tree[chr].lazy+=tree[root].lazy;
     tree[root].lazy=0;
}
void changelong(int root,int l,int r,int ll,int rr,LL val)
{
    if(l==ll&&rr==r)
    {
        tree[root].sum+=val*(r-l+1);
        tree[root].lazy+=val;
        return;
    }
    //pushdown(root,l,r);
    int mid=(l+r)>>1;
    if(mid>=rr)
     changelong(root<<1,l,mid,ll,rr,val);
    else if(mid<ll)
     changelong((root<<1)|1,mid+1,r,ll,rr,val);
    else
    {
         changelong(root<<1,l,mid,ll,mid,val);
         changelong((root<<1)|1,mid+1,r,mid+1,rr,val);
    }
    eval(root,l,r);
}
LL query(int root,int l,int r,int ll,int rr)
{
    if(l==ll&&r==rr)
    {
        return tree[root].sum;
    }
    pushdown(root,l,r);
    int mid=(l+r)>>1;
    if(mid>=rr)
        return query(root<<1,l,mid,ll,rr);
    else if(mid<ll)
        return query((root<<1)|1,mid+1,r,ll,rr);
    else
        return query(root<<1,l,mid,ll,mid)+query((root<<1)|1,mid+1,r,mid+1,rr);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        scanf("%lld",&num[i]);
    build_tree(1,1,n);
    char opert[30];
    while(m--)
    {
        scanf("%s",opert);
        if(opert[0]=='C')
        {
            int l,r;
            LL val;
            scanf("%d%d%lld",&l,&r,&val);
            changelong(1,1,n,l,r,val);
        }
        else
        {
            int l,r;
            scanf("%d%d",&l,&r);
            printf("%lld\n",query(1,1,n,l,r));
        }
    }
}
发布了52 篇原创文章 · 获赞 26 · 访问量 3185

猜你喜欢

转载自blog.csdn.net/qq_43803508/article/details/97159405