目录:
- 个人理解
- 时空复杂度分析
- 应用及例题
- 拓展
一、个人理解:
主席树的全称是可持续化权值线段树,是一种可以维护静态区间第K小的高级数据结构。
主席树的主要思想就是:保存每次插入操作时的历史版本,以便查询区间第 \(k\) 小。
因为主席树每次都要插入操作,所以是不能用堆式建树的(rt<<1
,rt<<1|1
),所以我们使用动态开点线段树,并用ls[]
和rs[]
保存当前节点的左右儿子。
联系前缀和,可以预处理达到 \(O(1)\) 的时间复杂度。我们发现主席树也满足这个性质,所以若需要统计 \([l,r]\) 的信息,只需要 \(O(1)\) 查询sum[r]-sum[l-1]
即可。
二、时空复杂度分析:
时间复杂度:
同线段树的时间复杂度:\(O(n\text{log}n)\)
空间复杂度:
我们是动态开点,所以一颗线段树只会出现 \(2n-1\) 个节点,而每次插入会增加 \(O(n\text{log}n)\) 个节点,则最坏情况下会有 \(2n-1+O(n\text{log}n)\) 个节点。故空间复杂度为:\(O(n\text{log}n)\) 。
在实际运用的时候,
ls[]
,rs[]
,sum[]
等数组都需要开 \(2^5\) 倍空间,即MAXN<<5
(MAXN
为 \(n\) 的值域)。注:在常数上减小内存消耗:
插入值时候先不要一次新建到底,能留住就留住,等到需要访问子节点时候再建下去。
这样理论内存复杂度依然是\(O(n\text{log}n)\),但因为实际上很多结点在查询时候根本没用到,所以内存能少用一些。
————cyendra
三、应用及例题:
静态区间第K小(P3834 【模板】可持久化线段树 1(主席树))
Description:
给定数列 \(\{a_n\}\) ,求闭区间 \([l,r]\) 的第 \(k\) 小的数。
Method:
先对数据进行离散化,然后按照权值建立线段树。
若要寻找 \([1,p]\) 的第 \(k\) 小,则从根节点开始处理。定义\(Son_{left}\) 表示左儿子的集合,\(Son_{right}\) 表示右儿子的集合。若 \(|Son_{left}|\ge k\) 时,说明第\(k\)小的数在左子树中,以左儿子为新的根向下递归更新,寻找左子树中第 \(k\) 小的数;反之,说明第\(k\)小的数在右子树中,以左儿子为新的根向下递归更新,寻找左子树中第 \(k-|Son_{left}|\) 小的数。
拓展一下,我们先预处理建树,得到 \(n+1\) 个版本的线段树(包括初始的线段树),编号为 \(0 \sim n\) 。
前文提到过,主席树满足前缀和查询的思想,故我们要求 \([l,r]\) 的第 \(k\) 小值,即可用
sum[r]-sum[l-1]
。Code:
#include<bits/stdc++.h> #define int long long #define Maxn 200010 using namespace std; inline void read(int &x) { int f=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=f; } int n,m; int ls[Maxn<<5],rs[Maxn<<5],sum[Maxn<<5],rt[Maxn]; int a[Maxn],ins[Maxn]; int len,tot=0; inline int getid(const int &x) { return lower_bound(ins+1,ins+len+1,x)-ins; } int build(int l,int r) { int root=++tot; if(l==r) return root; int mid=(l+r)/2; ls[root]=build(l,mid); rs[root]=build(mid+1,r); return root; } int update(int k,int l,int r,int root) { int dir=++tot; ls[dir]=ls[root],rs[dir]=rs[root],sum[dir]=sum[root]+1; if(l==r) return dir; int mid=(l+r)/2; if(k<=mid) ls[dir]=update(k,l,mid,ls[dir]); else rs[dir]=update(k,mid+1,r,rs[dir]); return dir; } int query(int u,int v,int l,int r,int k) { int mid=(l+r)/2; int x=sum[ls[v]]-sum[ls[u]]; if(l==r) return l; if(k<=x) return query(ls[u],ls[v],l,mid,k); else return query(rs[u],rs[v],mid+1,r,k-x); } signed main() { read(n),read(m); for(int i=1;i<=n;i++) { read(a[i]); } memcpy(ins,a,sizeof(ins)); sort(ins+1,ins+n+1); len=unique(ins+1,ins+n+1)-ins-1; rt[0]=build(1,len); for(int i=1;i<=n;i++) { rt[i]=update(getid(a[i]),1,len,rt[i-1]); } while(m--) { int l,r,k; read(l),read(r),read(k); printf("%lld\n",ins[query(rt[l-1],rt[r],1,len,k)]); } return 0; }
Warning:
ls[]
,rs[]
,sum[]
等数组都要乘上 \(2^5\) 。- 离散化取
lower_bound
时,是最后减去0开头的地址,而不是1开头的地址。(即是lower_bound(ins+1,ins+n+1,x)-ins
,而不是lower_bound(ins+1,ins+n+1,x)-ins-1
) - 查询时递归右子树时查找第 \(k-|Son_{left}|\) 小,而不是 \(k\) 小。
静态区间互异的个数(SP3267 DQUERY - D-query)
Description:
给定数列 \(\{a_n\}\) ,求闭区间 \([l,r]\) 的互异的个数。
Method:
扫描序列建立可持续化线段树,若此元素是第一次出现,就将对应的线段树中的位置加1;反之,就将上一个出现的元素对应的线段树中的位置减1,将此元素对应的线段树中的位置加1。
对于查询的 \([l,r]\) ,在第 \(r\) 个版本的线段树上查询位置 \(l\) ,对经过的区间中的和累加一下即可。
Code:咕咕咕……
四、扩展
动态区间第K小
Description:
给定数列 \(\{a_n\}\) ,支持两种操作:
- 将 \(pos\) 位置的值改为 \(p\) ;
- 查询闭区间 \([l,r]\) 的第 \(k\) 小值。
Mothod:
考虑树套树(树状数组套主席树)
咕咕咕……
标签:线段,ins,sum,查询,树中,复杂度,主席 来源: https://www.cnblogs.com/nth-element/p/11755820.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。