浅谈线段树

8 篇文章 0 订阅
3 篇文章 0 订阅

线段树

简介:

就是将一颗完全二叉树,每个节点存储的是一个节点区间[l,r]之中的值(比如最小值,最大和,总和……)最后在可以在 简短的时间复杂度之下实现修改区间,查询区间的功能!

总的来说,就是:先递归,再操作;先传输,再递归!

它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。

线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。
十分的强大!

初始化:

这里写图片描述

单节点初始化:

主要注意的就是这里是对于 一个区间进行 二分的操作
那么就是注意是 肯定是要先进行 build 之后 在进行分开的计算
不然就会 G_G!!

void build(int now,int start,int end,int a[])
{
    if(start==end)
        t[now].minvalue=a[start];
    else
    {
        int mid=(start+end)/2;
        build( now<<1,start,mid,a ); // 先递归完成了之后,再计算!
        build( now<<1|1,mid+1,end,a );
        t[now].minvalue=min(t[now<<1].minvalue,t[now<<1|1].minvalue);
    }
}

区间修改初始化:

这里的Addmark就是指 区间加的值,这里的值就是初始化成为 0 就好了!
之后 的用法就会利用到 叫一个延迟标记的 东东!

void build(int now,int start,int end,int a[])
{
    segTree[now].Addmark=0;
    if(start==end)
        segTree[now].tot=a[start],segTree[now].size=1;
    else
    {

        int mid=(start+end)/2;
        build( now*2  ,start,mid,a );
        build( now*2+1,mid+1,end,a );

        segTree[now].tot=segTree[now<<1].tot+segTree[now<<1|1].tot;
        segTree[now].size=segTree[now<<1].size+segTree[now<<1|1].size;
    }
}

询问的函数

这里写图片描述

单节点询问:

传进来的参数 有 qstart 与 qend 表示要询问的区间
而 start 与 end 指的是现在搜到的 区间
如果现在的 start~end 的区间的值 能够 被qstart ~qend给包括 那么既可以直接返回值了!因为如果继续二分下去 , 所求的值 还是 能够被大的值给包括!!
但是如果区间真的是完完全全的不重合了,那么我们也没有必要搜索了,因为是求最小值,所以直接返回INF!

int question(int root,int qstart,int qend,int start,int end)
{
    if(start>qend||end<start)
        return inf;
    if(qstart<=start&&qend>=end)
        return t[root].minvalue;
    int mid=(start+end)/2;
    return min(question(root<<1,qstart,qend,start,mid),
               question(root<<1|1,qstart,qend,mid+1,end));

}

区间询问:

那么区间询问同理!
其实这个Addmark 可以想象成为就是一个 “路障” 我们在向下询问的是后就会带上这个标记,也就是区间需要修改!!
只要是在被答案所寻求的区间里面包括了,那么就可以直接反回了!

long long question(int root,int qstart,int qend,int start,int end)
{
    if(start>qend||end<qstart)
        return 0;
    if(qstart<=start&&qend>=end)
        return segTree[root].tot;

    PushDown(root);
    int mid=(start+end)/2;
    return question(root<<1,qstart,qend,start,mid)+question(root<<1|1,qstart,qend,mid+1,end);

}

修改区间(单节点):

单点:

注: change 表示要修改的点的值
这里就是相当与就是递归到了最底层,在找到change之后对其惊醒修改,而别的并不惊醒修改,为什么,因为并不影响其结果!
但是注意了!,需要递归回来
为什么?
因为子节点的变化有可能会引起father节点及其以上的变化!所以在操作完了之后,要对子节点进行修改!!!

void update(int root,int change,int start,int end,int add)
{
    if(start==end)
    {
        if(start==change)
            t[root].minvalue+=add;
        return ;
    }
    int mid=(start+end)/2;
    if(change<=mid)
        update(root<<1,change,start,mid,add);
    else
        update(root<<1|1,change,mid+1,end,add);
    t[root].minvalue=min(t[root<<1].minvalue,t[root<<1|1].minvalue);
}

区间:

区间的一个Addmark 就是对于一个操作,PushDown 就是单纯的将其mark的信息传给它的子节点,为什么只需要这样呢,因为在update的操作里面,已经出现的递归程序,所以这样就相当于是连贯了起来,相当于形象来说就是代代相传!!(就是爷爷传给父亲,再是父亲穿给自己)

因为我们在下放延迟标记的时候,只是会将需要的时候,也就是搜索到的时候才会将延迟标记下放,但是我们如果不用到的话,那么子节点就是暂时是不需要动的,因为,者可以相当于是一个父子关系

比如,有什么困难,先是父亲先来解决,但是如果他解决不了了,那么才需要孩子了出面解决!但是,两个人所携带的信息是一模一样的!

比如父亲此时有战斗值 为 5 ,孩子是 2
但是孩子有延迟标记,之后才会 加上 比如 1
但是如果父亲能够解决,在直接叫父亲,但是实在不行,就要先Pushdown (father) 把father 的信息传下来!再叫孩子上!
但是先要叫孩子 的武力值加上 2 ,就是变成了 4 ,再来解决问题,不行再来,在来….(但是每一次都会看看父亲是不是可以!)

void PushDown(int root)
{
    if(segTree[root].Addmark!=0)
    {
        segTree[root<<1].Addmark+=segTree[root].Addmark;
        segTree[root<<1|1].Addmark+=segTree[root].Addmark;

        segTree[root<<1].tot+=segTree[root<<1].size*segTree[root].Addmark;
        segTree[root<<1|1].tot+=segTree[root<<1|1].size*segTree[root].Addmark;
        segTree[root].Addmark=0;
    }
}
void update(int root,int qstart,int qend,int start,int end,int add)
{
    if(start>qend||end<qstart)
        return ;
    if(qstart<=start&&qend>=end)
    {
        segTree[root].Addmark+=add;
        segTree[root].tot+=add*segTree[root].size;
        return ;
    }
    PushDown(root);

    int mid = ( start + end) / 2;
    update(root<<1, qstart, qend, start, mid, add);
    update(root<<1|1, qstart, qend, mid+1, end, add);
    //=====还是先递归完了之后,在进行操作=====
    segTree[root].tot =segTree[root<<1].tot+segTree[root<<1|1].tot;
}

注释:

所以整个线段树最难的应该就是在这里了!
单点的操作我就不讲了!关键是在 区间操作的* 延迟修改*
就单单拿一个 区间 (x,y) 加上 k 的一个简单的例子来说吧!
对于一个在线的操作 ,那么对于每一次都操作一遍是不是太麻烦了?所以我们想出来一个办法,在只需要询问的时候讲它输出来操作,别的时候就只需要用数字来记录一下就好了!

Addmark!!!
表示的就是 t[i].Addmark就是这个数字一会需要的操作的加上的值!

再来一个 Push的一个函数! 表示将它的father节点的Addmark的状态与其子节点相加,因为father节点就会包括子节点的嘛!
还有一个小疑问就是为什么是 “+=” 因为可能有一些子节点的状态在之后 会被释放或者是father节点有变化,所以就要是+=表示 只是将其状态的变化承接下来!
因为在函数PushDown 之中有一个归 0 的东东!

segTree[root].Addmark=0;

就是为了防止之后再一次询问的时候还会再算一遍 ,比如 Addmark =9; 算了之后这个区间就加上了9 ,但是之后 如果又来了 再加上5 ,不变的话就是 加上了 14 ,但是 事实上不是的,所以就是加上 5就好了,所以是记得归 0 !

完整代码:

单点

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <math.h>
#include <vector>
using namespace std;
const int N=2005;
const int inf=99999999;
int n,val[N];
struct edgytree
{
    int minvalue;
} t[N];
void build(int now,int start,int end,int a[])
{
    if(start==end)
        t[now].minvalue=a[start];
    else
    {
        int mid=(start+end)/2;
        build( now<<1,start,mid,a );
        build( now<<1|1,mid+1,end,a );
        t[now].minvalue=min(t[now<<1].minvalue,t[now<<1|1].minvalue);
    }
}
int question(int root,int qstart,int qend,int start,int end)
{
    if(start>qend||end<start)
        return inf;
    if(qstart<=start&&qend>=end)
        return t[root].minvalue;
    int mid=(start+end)/2;
    return min(question(root<<1,qstart,qend,start,mid),
               question(root<<1|1,qstart,qend,mid+1,end));

}
void update(int root,int change,int start,int end,int add)
{
    if(start==end)
    {
        if(start==change)
            t[root].minvalue+=add;
        return ;
    }
    int mid=(start+end)/2;
    if(change<=mid)
        update(root<<1,change,start,mid,add);
    else
        update(root<<1|1,change,mid+1,end,add);
    t[root].minvalue=min(t[root<<1].minvalue,t[root<<1|1].minvalue);
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&val[i]);
    }
    build(1,1,n,val);
    //printf("%d\n",t[3].minvalue);
    return 0;
}

区间:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <math.h>
#include <vector>
using namespace std;
const int N=100005;
int n,m,val[N];
// ´æÁË Ò»¸öÊÇÏë¼ÓµÄÖµ , ºÍÇø¼äÀïÃæ×ܹ²ÓÐÒ»¸ö ÔªËØ 
struct edgytree
{
    long long tot;
    int Addmark,Minusmark,size;
//¿ªÁËÒ»¸öÊý×é ×îºÃÊÇ 4 ±¶´óµÄÊý×é 
} segTree[4*N];
// ³õʼ»¯ 
void build(int now,int start,int end,int a[])
{
    //½«Æä±äΪ 0  
    segTree[now].Addmark=0;
    if(start==end)
        segTree[now].tot=a[start],segTree[now].size=1;
    else
    {

        int mid=(start+end)/2;
        build( now*2  ,start,mid,a );
        build( now*2+1,mid+1,end,a );
        segTree[now].tot=segTree[now<<1].tot+segTree[now<<1|1].tot;
        segTree[now].size=segTree[now<<1].size+segTree[now<<1|1].size;
    }
}
void PushDown(int root)
{
    // ÕâÒ»²½ÊÇ ½« root ½ÚµãµÄÁ½¸öº¢×ӽڵ㴫Èë×Ô¼ºµÄÐÞ¸ÄÖµ 
    if(segTree[root].Addmark!=0)
    {
        segTree[root<<1].Addmark+=segTree[root].Addmark;
        segTree[root<<1|1].Addmark+=segTree[root].Addmark;

        segTree[root<<1].tot+=segTree[root<<1].size*segTree[root].Addmark;
        segTree[root<<1|1].tot+=segTree[root<<1|1].size*segTree[root].Addmark;
        segTree[root].Addmark=0;
    }
}

long long question(int root,int qstart,int qend,int start,int end)
{
    if(start>qend||end<qstart)
        return 0;
    if(qstart<=start&&qend>=end)
        return segTree[root].tot;

    PushDown(root);
    int mid=(start+end)/2;
    return question(root<<1,qstart,qend,start,mid)+question(root<<1|1,qstart,qend,mid+1,end);

}
void update(int root,int qstart,int qend,int start,int end,int add)
{
    if(start>qend||end<qstart)
        return ;
    if(qstart<=start&&qend>=end)
    {
        segTree[root].Addmark+=add;
        segTree[root].tot+=add*segTree[root].size;
        return ;
    }
    //ÏòÏ´©²ÎÊý  
    PushDown(root);

    int mid = ( start + end) / 2;
    update(root<<1, qstart, qend, start, mid, add);
    update(root<<1|1, qstart, qend, mid+1, end, add);

    segTree[root].tot =segTree[root<<1].tot+segTree[root<<1|1].tot;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&val[i]);
    }
    build(1,1,n,val);
    for(int i=1; i<=m; i++)
    {
        int x,y,k,loc;
        scanf("%d",&loc);
        if(loc==1)
        {
            scanf("%d%d%d",&x,&y,&k);
            update(1,x,y,1,n,k);
        }
        else
        {
            scanf("%d%d",&x,&y);
            printf("%lld\n",question(1,x,y,1,n));
        }
    }
    return 0;
}

总结:

就是多学,多想!
讲一个线段通过树的方式拆开来!
可以推荐一个网址,如果不懂的话,说不定你们就是不懂!!呵呵!
大佬的线段树

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值