线段树进阶
线段树进阶
动态开点
定义
动态存储数据的线段树,可以优化空间复杂度
实现
为了避免
在递归时要记录点的左右区间,确保开点时能知道区间大小
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;
}
空间复杂度
不要像我一样 mknode 写错 tot 加了两次,搞得数据隔一点存一个然后 RE
还要注意 root 也会占线段树的空间为 O(n(\logn + 1))
线段树合并
需要用到动态开点。
不过就是遍历树然后刷新节点,然后pushup。时间复杂度要具体情况具体分析,比如雨天的尾巴,据分析。线段树合并过程中发生递归,p,q之一会被删除,所以merge函数被调用的次数不超过所有点数加一。
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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步