谈笑风生线段树(点修改)

好,今天,我们来看看线段树。
不得不说,线段树非常重要,这几天模拟考试就经常用到,而且NOIP也可能会用到。
前面我们说过了树状数组,树状数组确实要比线段树简单,代码容易写,但是其功能不如线段树。也就是说,树状数组能做的事,线段树都能做,而有些线段树能做的事,树状数组是做不了的,所以,我们还是要来看看线段树。
线段树其实也并不是非常复杂,我们今天先来看看线段树的点修改。
在讲解线段树的操作之前,我们先来看看什么是线段树,我们总得知道我们正在用什么样的一个东西。
什么是线段树呢?
其实就是一个树,它的每个结点都是一个线段,比如:
线段树
这就是一个线段树。是不是很形象?
那么,我们要这一个线段树干什么?
很明显,我们的每个结点是有自己的附加值的,否则这棵树就显得毫无意义。
那么,我们有为什么要用线段树呢?
和树状数组一样,因为它快呗!
注意线段树的区间是二分的,所以在询问某些值的时候,就会比普通的数据结构更快。(就这样简单的解释一下,不再详细严谨地解释)
今天我们的修改仅限于点修改,
但是我们的查询可就多了,我们今天会讲三个,区间最大,区间最小和区间内和。
今天我的解释会紧跟着代码。

一、最小值

1、点修改

void update(int rt,int l,int r,int x,int p){
    if(l==r){
        minv[rt]=p;
        return;
    }
    int m=(l+r)/2;
    if(x<=m){
        update(rt<<1,l,m,x,p);
    }else{
        update(rt<<1|1,m+1,r,x,p);
    }
    minv[rt]=min(minv[rt<<1],minv[rt<<1|1]);

} 

先对变量进行简单的解释:
rt是当前区间[l,r]的编号,我们的编号是按树从上到下、从左到右编号的,且根节点为1.
x是要修改的位置,p是要修改为的值;
minv[rt]表示编号为rt的区间的最小值。
这样的话这段代码就很好理解了,我简单解释一下。
首先,判断当前区间的l和r是否相等,若相等,那么很明显,这是个单点区间,也是我们要修改的位置,那么直接把该区间的最小值改掉即可,因为这个区间只有它,所以最小值一定是它。
接下来求m,即区间中点。
判断修改的点是在左半边区间(左结节点)还是在右半区间(右子结点),更新对应的区间。标号的对应应该很好理解,我用的是位运算,其实也就是编号为rt的结点的左右子结点的编号分别为rt*2和rt*2+1,写成位运算就成了我的代码中的那个样子。
最后修改一下当前这个区间的最小值,即左子结点和右子结点最小值中的最小值,这样我们的修改程序就完成了。

2、最小值查询

int query(int rt,int l,int r,int ql,int qr){
    if(ql<=l&&qr>=r){
        return minv[rt];
    }
    int m=(l+r)/2,ans=0x3f3f3f3f;
    if(ql<=m){
        ans=min(ans,query(rt<<1,l,m,ql,qr));
    }
    if(qr>m){
        ans=min(ans,query(rt<<1|1,m+1,r,ql,qr));
    }
    return ans;
}

变量该解释的都在前面解释过了,这里不在赘述。
我直接讲解代码。
首先判断所求区间是否包含当前区间,若包含,那么我们直接返回该区间的在最小值即可。
否则,我们求一下当前区间的中点,然后再判断所求区间设计当前区间的左子结点还是右子结点,分别求出最小值,然后取最小即可,最后输出答案。

二、最大值

这里我就不再解释,因为和最小值一样,代码放给大家。

1、点修改

void update(int rt,int l,int r,int x,int p){
    if(l==r){
        maxv[rt]=p;
        return;
    }
    int m=(l+r)/2;
    if(x<=m){
        update(rt<<1,l,m,x,p);
    }else{
        update(rt<<1|1,m+1,r,x,p);
    }
    maxv[rt]=max(maxv[rt<<1],maxv[rt<<1|1]);

} 

2、区间最大值查询

int query(int rt,int l,int r,int ql,int qr){
    if(ql<=l&&qr>=r){
        return maxv[rt];
    }
    int m=(l+r)/2,ans=0x3f3f3f3f;
    if(ql<=m){
        ans=max(ans,query(rt<<1,l,m,ql,qr));
    }
    if(qr>m){
        ans=max(ans,query(rt<<1|1,m+1,r,ql,qr));
    }
    return ans;
} 

和最小值相比,好像除了把min改成max,其他都没有变化。

三、区间和

1、点修改

void update(int rt,int l,int r,int x,int p){
    if(l==r){
        sum[rt]=p;
        return;
    }
    int m=(l+r)/2;
    if(x<=m){
        update(rt<<1,l,m,x,p);
    }else{
        update(rt<<1|1,m+1,r,x,p);
    }
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];

} 

依然主要是名字的变化,还有就是原来是两个子结点取最小,现在是求和。

2、区间和查询

int query(int rt,int l,int r,int ql,int qr){
    if(ql<=l&&qr>=r){
        return sum[rt];
    }
    int m=(l+r)/2,ans=0x3f3f3f3f;
    if(ql<=m){
        ans+=query(rt<<1,l,m,ql,qr);
    }
    if(qr>m){
        ans+=query(rt<<1|1,m+1,r,ql,qr);
    }
    return ans;
}

由原来的取最小,变成了求和,很好理解,相信你有举一反三的能力,不再赘述。

结束语

这就是线段树最简单的一类了,今天就讲到这,大家注意复习总结。
至于练习题,我会在把线段树的区间修改讲完后,出点综合性的数据结构题,尽请期待!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值