主席树(可持久化线段树)
主席树的定义与作用
主席树,也称可持久化线段树(主席树为什么叫主席树?据说因为它是一个名字缩写为
什么是可持久化线段树呢,即为一颗记录了所有更新过程的线段树。能够处理出从第
前置知识
值域线段树
要学主席树,我们就要先学值域线段树。
值域线段树的区间存的并不是节点信息,而是在值在某一范围内的数的个数。
如图就是一棵值域线段树。
1号节点 存储的是 大于等于1小于等于4的数字个数
2号节点存储的是 大于等于1小于等于2的数字个数
3号节点存储的是 大于等于3小于等于4的数字个数
4号节点存储的是 等于1的数字个数
5号节点存储的是 等于2的数字个数
6号节点存储的是 等于3的数字个数
7号节点存储的是 等于4的数字个数
值域线段树的查询也挺简单的,若要查询这段区间内的第
动态开点线段树
从值域线段树到主席树
上述值域线段树做法只能从所有数中找出第
考虑建
但是建
接下来的操作就是主席树的精妙所在了。
比如下面一组数,我们该如何对它进行操作呢?
3,5,8,6,7,2,1,4
我们建一棵值域线段数,将
它会是下面这个样子
如果我们不做优化的话我们将继续建一棵树并把
新的值域线段树会长这个样子
我们发现每次加入一个新的元素时更改的部分只会是一条链,而其他的部分则是无用的节点,自然的我们就想到能否让这两棵树共用这部分节点来减少节点的数量和建树的时间。
怎么操作呢?我们先建根结点,递归去看左孩子和右孩子,会发现左孩子的信息和上一棵树的是一样的,所以让他的左孩子直接指向上一棵树的左孩子,体现在代码中就是ls[x]=ls[last]
继续地递归且按照这种方式操作一直到叶子节点,这样我们就初步完成一颗最简单的主席树了。
以接下来的节点为根的树要在前一棵树的基础上建。整颗主席树完成后就会是这个样子。
有了主席树,再结合前缀和的思想,我们就可以对区间第
比如现在要求区间内 [l,r] 的第
因为可持久化线段树不再是一棵完全二叉树,所以我们不能再用层次序编号,而是改为直接记录每个节点的左、右子节点编号。因为每次修改都会创建
容易发现,可持久化线段树维护了每次操作之后线段树的历史形态。
从
可持久化线段树难以支持大部分“区间修改”。当一个节点下传延迟标记时,一旦我们创建它左右子节点
代码
namespace SegmentTree{
struct node{//左右孩子,区间和
int ls,rs,sum;
}t[M*50];//一共m次询问,每次最多新开2*logn个节点,所以空间复杂度是O(mlogn)
int tot=1,root[N]={1};//总结点个数,根节点编号
inline void Pushup(int x){
int &lx=t[x].ls,&rx=t[x].rs;
t[x].sum=t[lx].sum+t[rx].sum;
}
inline void update(int &x,int l,int r,int pos,int val,int now){
if(!x) x=++tot;
if(l==r){
t[x].sum=t[now].sum+val;
return;
}
int mid=(l+r)/2;
if(pos<=mid){
update(t[x].ls,l,mid,pos,val,t[now].ls);
t[x].rs=t[now].rs;
}
else{
t[x].ls=t[now].ls;
update(t[x].rs,mid+1,r,pos,val,t[now].rs);
}
Pushup(x);
}
inline int query(int x,int l,int r,int fr,int k){
if(!x) x=++tot;
if(l==r) return l;
int mid=(l+r)>>1;
if(t[t[x].ls].sum-t[t[fr].ls].sum>=k) return query(t[x].ls,l,mid,t[fr].ls,k);
else return query(t[x].rs,mid+1,r,t[fr].rs,k-t[t[x].ls].sum+t[t[fr].ls].sum);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)