入坑又爱又恨的线段树
线段树是将一段下标 1-n 的数组划分为一颗二叉搜索树,何为二叉搜索树呢,如下图所示
每一个可以继续延申的节点都有两个儿子,
且每一个节点下标k的两个儿子节点的下标分别是k+k和k+k+1,线段是利用这个特性,将1-n的一个区间不断分为两个又两个区间,
线段树的操作主要分为三个部分
建树(线段树初始化)
插入(对区间进行操作)
查找(获取区间的值)
每次的插入和查找的复杂度均为O(logn),总复杂度O(nlogn).
不难发现的是,我们可以用每一个节点表示两个儿子的和,从下网上递归得出每一个区间得和。
1 | void build(int k, int l, int r) { |
当我们给下标为4的位置+1时,可以发现他的往上的节点都会+1,改递归操作应从上往下进行,
开始发现4∈(1-4),则(1-4)+1,
然后发现4∈(3-4),则(3-4)+1,
最后4∈(4),则(4)+1。
1 | void in(int k, int l, int r, int x, int v) { |
进行区间加值时
我们可以对这个操作进行优化,当进行区间加值时,如对3-4区间+1
开始发现(3-4)∈(1-4),则(1-4)+1,
然后发现(3)∈(1-2),4∈(3-4),则(1-2)+1,(3-4)+1,
最后3∈(3),4∈(4),则(3)+1,(4)+1。
在我们对区间进行范围性加法运算的时候,需要用个数组对节点下标进行标记,表示加法运算到该节点结束
要注意的是
我们使用标记是使得所有包含更改区间的节点都能更新值,且到完全覆盖修改区间的时候标记不下传
刚开始我想过,能不能不用 标记,直接包含的每段区间都 f[k]+=(y-x+1)*t; 就好了,结果是不行的,
如果不用标记,我修改了(1-4)的值+1,会在第一个节点停止,当我查询(2-3)的时候,会一直递归找到(2),(3)的值,发现并不能获取更新后的值,所以需要标记来记录更新。
1 | void in(int k,int l,int r,int x,int y,ll t){ |
至此我们便可以对区间进行范围性加法操作,
但要如何查询呢
当我们要查询1-3的区间和时,
发现(1-3)不完全∈(1-4),却可以发现其儿子(1-2),(3-4)中,都包含了查询区间,于是把查询区间分为两块,分别对(1-2),(3-4)进行查询(1-2),(3)
接着我们发现(1-2)∈(1-2),则返回答案,
(3)不完全∈(3-4),发现其左儿子包含了3,则递归左儿子,然后查询到了(3),便返回(3)
1 | ll cal(int k,int l,int r,int a,int b,ll ans){ |
另外对于n个数的数组,把他递归分成一颗二叉搜索树,其的数组范围就是结点的数量,应该开到4n
洛谷原题1线段树加法
answer code:
1 |
|
🥰🥰🥰😘
2022-9-7 23:24
待更新区间乘法…
太难啦太难啦,为什莫这么烦 QAQ
洛谷原题2线段树乘法
当我们需要对区间进行乘法操作和加法时,一个标记肯定不满足,所以需要另开一个数组标记该结点需要的乘数,并且这两种操作方法对于取模操作是不封闭的,每一次都可以取模运算。
此外我们需要用一个函数来维护区间内的标记,使得标记下传,对于题目给的加法和乘法操作,在下传标记的时候要么先加后乘,要么先乘后加,而我们采用的是先乘后加。()
//push维护标记下传
1 | void push(int k,int l,int r){ |
为了使得标记值每次都能往下传递,所以每次操作都应该用一次push
来保证标记传递和标记初始化
区间乘法,t表示乘上的数
1 | f[k]=(f[k]*t)%p; |
区间加法
1 | add[k]=(add[k]+t)%p; |
1 |
|
差不多吧,总觉得还是有个点不是很透彻,慢慢悟吧
2022-9-8 19:54
微博摸张长图