道隐于小成,言隐于荣华。.|

⅔钱强

园龄:1年4个月粉丝:5关注:17

线段树进阶

线段树进阶

动态开点

定义

动态存储数据的线段树,可以优化空间复杂度

实现

为了避免 N<<1 ,不再使用完全二叉树存储,而记录左右儿子 ls,rs 此外需要 tot 记录已经开了多少点

在递归时要记录点的左右区间,确保开点时能知道区间大小

void modify(int &p,int L,int R,int l,int r,int v){//big->pointlr small->modifylr
	if(!p)p = mknode();
	t[p].l = L,t[p].r = R;
	int siz=R-L+1;
	if(l<=L&&R<=r){
		t[p].sum += 1ll*siz(p)*v;
		t[p].add += v;
		return;
	}
	pushdown(p);
	int mid=L+R>>1;
	if(l<=mid)
		modify(t[p].ls,L,mid,l,r,v);
	if(r>mid)
		modify(t[p].rs,mid+1,R,l,r,v);
	pushup(p);
}
long long query(int p,int L,int R,int l,int r){
	if(!p)return 0;//没有该节点 返回0
	if(l<=L&&R<=r)return t[p].sum;
	int mid=L+R>>1;
	long long ans=0;
	pushdown(p);
	if(l<=mid)ans+=query(t[p].ls,L,mid,l,r);
	if(r>mid)ans+=query(t[p].rs,mid+1,R,l,r);
	return ans;
}

空间复杂度

O(min(mlogn,n2))

不要像我一样 mknode 写错 tot 加了两次,搞得数据隔一点存一个然后 RE

还要注意 root 也会占线段树的空间为 O(n(\logn + 1))

线段树合并

需要用到动态开点。

不过就是遍历树然后刷新节点,然后pushup。时间复杂度要具体情况具体分析,比如雨天的尾巴,据分析。线段树合并过程中发生递归,p,q之一会被删除,所以merge函数被调用的次数不超过所有点数加一。 O((m+n)log|V|)

int merge(int p,int q,int L,int R){
	if(!p)return q;
	if(!q)return p;
	if(L==R){
		t[p].dat += t[q].dat;
		t[p].pos = t[p].dat?L:0; 
		return p;
	}
	int mid=L+R>>1;
	t[p].ls = merge(t[p].ls,t[q].ls,L,mid);
	t[p].rs = merge(t[p].rs,t[q].rs,mid+1,R);
	pushup(p);
	return p;
}

优化建图

一个区间每个点对一个点进行连边。可以建立线段树,线段树内部边权根据问题而定。如果要分入边和出边,就建两棵树,入树和出树,注意两棵树的叶子节点是一样的,可以使用动态开点实现。

标记永久化

适当的时候,不下传标记,而是查询时一层层递归,累加答案,优化时间复杂度。

可持久化

参见主席树

练习题

学而不思则妄,思而不学则殆

【模板】线段树 1 提示:动态开点

【模板】线段树合并 提示:线段树合并,树上差分

【模板】线段树分裂

Legacy 提示:线段树优化建图

参考资料

线段树合并时间复杂度证明

本文作者:⅔钱强

本文链接:https://www.cnblogs.com/life-of-a-libertine/p/18017226

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ⅔钱强  阅读(12)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起