权值线段树
在一些计数问题中,线段树用于维护值域(一段权值范围),这样的线段树也称为权值线段树。
动态开点
为了降低空间复杂度,我们可以不建出整棵线段树的结构,而是在最初只建立一个根节点,代表一整个区间,当需要访问线段树的某棵子树时,再建立代表这个子区间的结点。采用这种方法维护的线段树称为动态开点线段树。动态开点线段树抛弃了完全二叉树父子结点二倍的编号原则,改为用变量记录左右子节点编号
建立线段树code
struct Tree{
int l, r;
int dat;
}tree[MAXN << 1];
int build(){
tot++;
tree[tot].l = tree[tot].r = tree[tot] = 0;
return tot;
}
//初始化
tot = 0;
root = build();
// pos位置加上val
void insert(int p, int l, int r, int pos, int val){
if(l == r){
tree[p].dat += val;
}
int m = (l + r) >> 1;
if(pos <= m){
if(!tree[p].l) tree[p].l = build();
insert(tree[p].l, l, m, pos, val);
}
else{
if(!tree[p].r) tree[p].r = build();
insert(tree[p].r, m+1, r, pos, val);
}
pushup(p);
}
线段树合并
如果有若干个线段树同时维护区间
[
1
,
n
]
[1,n]
[1,n],显然它们对子区间的划分是一致的,可以进行线段树合并。
合并两棵线段树时,用两个指针
p
,
q
p,q
p,q从两个根节点出发,以递归的方式同步遍历这两棵线段树。
1、若
p
,
q
p,q
p,q之一为空,则以非空节点作为下一个节点
2、若
p
,
q
p,q
p,q都非空,则递归合并左右子树,然后以
p
p
p节点作为合并后的节点
p
u
s
h
u
p
pushup
pushup,如果到达叶子节点,则把他们的值按要求操作即可。
int merge(int p, int q, int l, int r){
if(!p) return q;
if(!q) return p;
if(l == r){
tree[p].dat += tree[q].dat;
return p;
}
int m = (l + r) >> 1;
tree[p].l = merge(tree[p].l, tree[q].l, l, m);
tree[p].r = merge(tree[p].r, tree[q].r, m+1, r);;
pushup(p);
return p;
}
我们发现,合并过程中, p , q p,q p,q必有一个被删除,所以我们合并 m e r g e merge merge 的次数不会超过线段树总结点加一,合并过程复杂度为 O ( m l o g n ) O(mlogn) O(mlogn)