线段树之入门

·线段树

emmm作为一个很基础的数据结构
线段树不管在时间还是空间上表现都很棒
利用的分治的想法
来一道模板题

如题,已知一个数列,你需要进行下面两种操作:

1.将某个数修改成x

2.求出某区间每一个数的和

N<=100000,M<=100000

题目大意如上
普通的朴素模拟法肯定是不行的(当然你的computer是量子计算机我也没办法/摊手)
那么线段树就Up了
首先我们要利用以前学习【归并排序】 的思想(什么?如果你没学过归并排序建议先别学线段树,LuoGu对普及选手的待遇很棒,可以考虑一下那里的题解或Blog)
将给出的序列,分治!

建树

先假设给出的序列长度为7
序列为{1,2,3,4,5,6,7}
这里写图片描述
那么我们将树建成如上的情况
树上的每个节点表示一段序列的和
1-7的节点表示1-7的和,同理x-y的节点表示x-y的和
那么很easy
我们用sum[x,y]表示x-y的节点和
那么sum[1,7]=sum[1,4]+sum[5,7]
同理可递归sum[1,4]=sum[1,2]+sum[3,4],sum[5,7]=sum[5,6]+sum[7,7]
通过这个发现我们可以建立一棵线段树
关于线段树的存储有很多方法
个人认为最方便的是像堆一样(x的左子节点是【x*2】右子节点是【x*2+1】)
代码如下

    void Build_Tree(int now,int L,int R,int a[]){//now为当前节点编号 L为当前节点所表示的区间的左边界,R为右边界 a为初始序列的数组 
        if (L==R){tree[now].key=a[L];return;}//当L==R 也就是建树到了叶节点 这时候更新tree的key值,在本题里key值就是sum 
        int mid=((R-L)>>1)+L;//求L到R的中点 等同于(L+R)/2  之所以这么写是为了避免(L+R)的结果越界(或许这里没有什么关系但这毕竟是个好习惯)
        Build_Tree(now<<1,L,mid,a),Build_Tree((now<<1)|1,mid+1,R,a);//建立左子树和右子树 
        tree[now].key=tree[now<<1].key+tree[(now<<1)|1].key;//通过左子树和右子树的key值来更新当前的key值 
    }

这就是建树的过程
接下来看题目的两个操作

更新

题目要求更新单个序列的值
也就是更改线段树中单个叶子节点的值
我们会发现更新了叶子节点值后其父节点以及父节点的父节点一直到根节点的值都会随之改变
如 我们将第五个元素原本的5改成6
修改后的树为下图
这里写图片描述
显而易见节点的值改变情况
而我们在代码中只需递归便可进行这个操作
代码如下

    void Updata(int now,int L,int R,int x,int p){//now为当前节点编号 L为当前节点所表示的区间的左边界,R为右边界 x为要修改的序列中的数的编号 p为要加上的数 
        if (L==R&&L==x){tree[now].key=p;return;}//完成叶节点的更新
        int mid=((R-L)>>1)+L;
        if (x<=mid) Updata(now<<1,L,mid,x,p);else Updata((now<<1)|1,mid+1,R,x,p);//如果x在中点的左边 那么向左子树更新,不然向右子树更新
        tree[now].key=tree[now<<1].key+tree[(now<<1)|1].key;//通过更新后的左子树和右子树的key值来更新当前的key值 
    }

查询

比如查询sum[1,5]
我们可以将这个询问拆分 sum[1,5]=sum[1,4]+sum[5,5]
也就是说可以将sum[x,y]拆分成各个线段树节点加和
这样我们可以再线段树节点中找到相对应的节点
只需在递归的时候统计就ok了
代码实现如下

    int Query(int now,int L,int R,int QL,int QR){//now为当前节点编号 L为当前节点所表示的区间的左边界,R为右边界 QL为询问区间的左边界 QR为询问区间的右边界 
        if (L==QL&&R==QR){return tree[now].key;}//找到了刚好与询问区间相匹配的节点,那么就没有必要继续递归下去了,只需返回该节点的key值
        int mid=((R-L)>>1)+L;
        if (QL<=mid&&QR<=mid) return Query(now<<1,L,mid,QL,QR);//分类讨论 如果询问区间整个在中点的左边 只需要向左子树递归就ok 
    else if (QL>mid&&QR>mid) return Query((now<<1)|1,mid+1,R,QL,QR);//同理如果询问区间整个在中点的右边 只需要向右子树递归
    else return Query(now<<1,L,mid,QL,mid)+Query((now<<1)|1,mid+1,R,mid+1,QR);//如果都不是以上两个情况 那么中点就在询问区间中 只需要将询问区间按中点分为两个询问区间 分别向左右子树递归 
    } 

时空效率

线段树的节点个数显然由序列长度n决定
可以发现节点个数为N*(log(n))
那么建树效率也为O(N*(log(n)))
查询和修改的效率由树的深度决定 为O(log(n))
空间效率为树的节点个数N*(log(n))

因为本人也是蒟蒻一个
如有dalao发现错误欢迎指出
感激不尽

猜你喜欢

转载自blog.csdn.net/Oevln_/article/details/80257672
今日推荐