线段树合并和分裂
线段树合并
空间复杂度,一般是根据操作次数来计算的,或者按照题目的空间,算出最大开多少数组。
根据感性理解,线段树的深度是\(\lceil log_2n\rceil\)的,反正\(d = \lfloor log_2n\rfloor+1\)肯定够。
那\(m\)次操作,注意这个操作不一定是原题中的询问,而是你对于线段树的操作次数,总共就要开\(O(md)\)个点。
比如模板题操作次数就是\(4m\)(\(m\)为题目修改次数)。
然后就是这种写法:
int merge(int l,int r,int x,int y) {
if (!x||!y) return x+y;
int mid=l+r>>1;
if (l==r) {
tr[x].max+=tr[y].max;
if (!tr[x].max) tr[x].id=0;
else tr[x].id=l;
return x;
}
tr[x].ls=merge(l,mid,tr[x].ls,tr[y].ls);
tr[x].rs=merge(mid+1,r,tr[x].rs,tr[y].rs);
pushup(x);
return x;
}
是舍弃了原先的两棵线段树,此时只能保证x
的信息是正确的,全部merge
之后y
的结构会改变。
但是本题中我们每次查的时候,只会查询根的值,这时为什么根的值也会变呢?
合理来说,根不会被挂到另外一个根上,所以应该是正确的。
对拍良久……终于发现如果\(u\)是\(v\)的父亲,\(u\)没有被修改过,那么\(u\)的根就是空,merge
之后\(u\)的根会直接变成\(v\)的根,于是根节点的值就被改变了(这个根变成了\(u\)的线段树,不是\(v\)的线段树了)……
好坑……
example
7 3
1 2
2 3
2 4
4 5
1 6
6 7
5 6 1
6 1 5
1 3 4
时间复杂度:每次merge
只会遍历到在x
和y
中都存在的节点和它们挂着的儿子,挂着的儿子和它们自身的数量同阶,可以认为我们将y
删去了,所以每次的复杂度就是删去的y
的个数。
因为一个数被删,一定要在merge
的时候被遍历到,而删去之后就不会被遍历到了,所以一个点至多会被删去一次,而总点数是\(mlogn\)的,所以总的复杂度是\(O(mlogn)\)的。
线段树分裂
终于过了!!!
中间变量忘开long long
寄了……
分裂类似FHQ Treap
,可以有按值和按大小两种。
每次分讨一下,然后更新两棵树就行了。
每次split
至多新增\(logn\)个节点,一开始有\(4n\)个节点,最多有\(n+mlogn\)个点,merge
与上面分析不同的是,点的总数一直在增加,但是我们不管中间过程,无论如何删去的点数一定不超过插入的点数,所以总共删去的点数不超过\(n+mlogn\),其它操作都是显然\(mlogn\)的,总的复杂度\(O(mlogn)\)。
空间也是\(n+mlogn\)。