可持久化权值线段树(主席树)笔记
可持久化权值线段树(主席树)笔记
区别于普通线段树,权值线段树维护的信息不同
- 普通线段树:节点区间是序列的下标区间,维护区间最值,区间和等信息
- 权值线段树:节点区间是序列的值域,维护值域内数出现的次数
*图片引自董晓算法
给定一个区间,询问该区间内的第 \(k\) 小值是多少,暴力的方案就是每次都开一颗线段树,由于空间受限我们并不能这样做
一种可行且有效的办法就是建立主席树,保存每次插入时的历史版本,下一次直接从这个版本上衍生节点即可
#define lc(x) tr[x].ch[0] #define rc(x) tr[x].ch[1] struct node{ int ch[2];//左右儿子 int s;//记录值域内的元素个数 }; int n,m; int a[N]; node tr[N*22];//建议开到log^2(n)的大小 int root[N],idx;
首先是建树过程,与线段树类似,递归创建值域 \([1,n]\) 内的各个节点,以及对节点进行编号
build(root[0],1,n); void build(int &x,int l,int r){ x=++idx;//更新编号 if (l==r) return;//递归出口 int m=l+r>>1; build(lc(x),l,m);//递归建立左子树 build(rc(x),m+1,r);//递归建立右子树 }
需要对 \(x\) 传入引用,即进行写入操作,更改节点原有值
插入操作,建立主席树,递归创建各个版本的线段树
insert(root[i-1],root[i],1,n,v); void insert(int x,int &y,int l,int r,int v){//插入v y=++idx;//对节点进行编号 tr[y]=tr[x];//复制上个版本的信息到当前版本 tr[y].s++;//因为插入了一个数,所以该值域内的元素个数加1 if (l==r) return; int m=l+r>>1;//二分 if (v<=m) insert(lc(x),lc(y),l,m,v);//依然传入上个版本的儿子与当前版本的引用 else insert(rc(x),rc(y),m+1,r,v); }
x
传入的是上一个版本的线段树的内容,不引用进行只读操作,y
传入的是当前版本来进行建树,写入操作
同样的 lc(x)
与 lc(y)
相当于两个指针,前者指向上一个版本,后者指向当前版本,进行同步搜索
查询操作,查询区间 \([l,r]\) 内的第 \(k\) 小
考虑普通情况:查询区间 \([1,r]\) 内的第 \(k\) 小,那么找到插入 \(r\) 时的版本在树上进行搜索即可
那么求 \([l,r]\) 上的第 \(k\) 小,可以采取类似前缀和的处理方式,分别查询 \([1,l-1]\) 与 \([1,r]\) 的信息,做差即可
query(root[l-1],root[r],1,n,k); int query(int x,int y,int l,int r,int k){ if (l==r) return l; int m=l+r>>1; int s=tr[lc(y)].s-tr[lc(x)].s; if (k<=s) return query(lc(x),lc(y),l,m,k); else return query(rc(x),rc(y),m+1,r,k-s);//减去左子树值域内增加元素的个数 }
其中 int s=tr[lc(y)].s-tr[lc(x)].s;
计算的是两个版本节点对应左子树值域内元素的增加个数
如果左子树值域内增加的个数小于等于我们需要求的第 \(k\) 小,那么这个元素必然在左子树值域内增加的元素中
如果左子树值域内增加的个数大于我们需要求的第 \(k\) 小,那么这个元素必然在右子树值域内增加的元素中
考虑需要处理的数值值域较大但数量较少时,可以采取离散化的操作,一一映射下标
vector<int> v; for (int i=1;i<=n;i++){ cin>>a[i]; v.push_back(a[i]); } sort(v.begin(),v.end());//从小到大排序 v.erase(unique(v.begin(),v.end()),v.end());//去重 int getid(int x){//得到离散化后的值,一一映射关系 return lower_bound(v.begin(),v.end(),x)-v.begin()+1; }
主席树查询区间内小于等于 \(k\) 的元素个数
int rankquery(int x,int y,int l,int r,int k){ if (r<=k) return tr[y].s-tr[x].s;//如果k比区间内所有值都要大,那么直接返回区间内元素总数 if (l>k) return 0;//如果k比区间内所有值都要小,那么直接返回区间内元素总数 int m=l+r>>1;//二分 return rankquery(lc(x),lc(y),l,m,k)+rankquery(rc(x),rc(y),m+1,r,k);//递归左右子树搜索 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)