主席树是什么
先思考这样一个问题,给定一段序列,和若干个询问,每次查询区间[L,R]中第k小的数。
挂上模板题:洛谷3834
现在来想一下具体做法,首先我们知道求整个序列的第k大可以用权值线段树(假设已经会了 ),那么我们想如何求[L,R]的呢,我们想如果已经知道了[1,L]和[1,R]这两棵权值线段树是不是就能够得到答案了呢(当然是了),我们还需要用求全局第k小的方法在向下递归的过程中不断从[1,R]中把[1,L]摘出来即可。
主席树的具体实现
用了上面方法我们知道了解决这个问题的方法,但是这样的操作需要开n棵权值线段树,内存肯定是吃不消的,那么有什么方法可以减少内存开支呢,我们发现在新添加一个元素的时候实际上只修改了某一条链,如果我们只是把链存起来的话,那么空间开支就会变成nlogn。具体的实现方法就是说在每次新插入一个值的时候,新开一个根,将所有新修改的元素组成的这条链和原来已有的树相连,每个节点只保存左右儿子节点而不存父亲节点。如下图所示:
我之前所看到的大多数博客都采用了数组的写法,这里我使用的是指针的写法具体插入某一个值的操作为:
void Insert(LE &k,LE kk,int val,int L,int R){
k=new Node;
k->val=0;
k->f=kk->f;
k->t=kk->t;
if(k->f==k->t){
k->val=kk->val+1;
return;
}
int mid=(k->f+k->t)>>1;
if(val<=mid){
k->r=kk->r;
Insert(k->l,kk->l,val,L,mid);
}
else{
k->l=kk->l;
Insert(k->r,kk->r,val,mid+1,R);
}
k->val=k->l->val+k->r->val+k->val;
}
由于插入一个数都会对后来插入的数产生影响,因此kk代表的是上一次插入的树所形成的权值线段树,当此次修改影响了左子树时,应当加上k->r=kk->r以保证将新生成的连连回到原来的树上,插入到右子树时也是同理。
一些细节
在这里再补充一下,由于一般主席树处理的数据较大(大概int范围),而主席树是nlogn复杂度的算法,因此一般都会将主席树所处理的数据进行离散化。
对于离散化所用到的STL里的unique函数,他所能处理的是将一组数据相邻的相同元素进行去重,因此在离散化之前要把数组先进行排序。还有就是unique函数在离散化是完成的并不是“去重”,而是将重复的元素放到序列的尾端,同时返回不同元素的个数,例如a[10]=1,3,3,4,6,6,6,7,8,9
m=unique(a+1,a+n+1);
则此时m=7,a[10]=1,3,4,6,7,8,9,3,6,6
完整的主席树代码
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
typedef struct Node{
Node *l,*r;
int f,t;
int val,num;
}*LE;
LE Null,root;
LE Root[800000];
int Cnt,a[400000],b[400000],c[400000];
void Build(LE &k,int L,int R){
if(k==Null){
k=new Node;
k->val=0;
k->l=Null;
k->r=Null;
k->f=L;
k->t=R;
}
if(L==R)return;
int mid=(L+R)>>1;
Build(k->l,L,mid);
Build(k->r,mid+1,R);
}
void Insert(LE &k,LE kk,int val,int L,int R){
k=new Node;
k->val=0;
k->f=kk->f;
k->t=kk->t;
if(k->f==k->t){
k->val=kk->val+1;
return;
}
int mid=(k->f+k->t)>>1;
if(val<=mid){
k->r=kk->r;
Insert(k->l,kk->l,val,L,mid);
}
else{
k->l=kk->l;
Insert(k->r,kk->r,val,mid+1,R);
}
k->val=k->l->val+k->r->val+k->val;
}
int Query_Rankk(LE k1,LE k2,int k){//k1 is R,k2 is L-1
if(k1->f==k1->t)
return k1->f;
int sz=k1->l->val-k2->l->val;
if(sz>=k)Query_Rankk(k1->l,k2->l,k);
else Query_Rankk(k1->r,k2->r,k-sz);
}
int main()
{
int n,m,k,L,R,Top=0,M;
Null=new Node;
Null->val=0;
Null->f=Null->t=0;
Null->l=Null;
Null->r=Null;
scanf("%d %d",&n,&M);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
m=unique(b+1,b+1+n)-b-1;
for(int i=0;i<=n;i++)Root[i]=Null;
Build(Root[0],1,m);
for(int i=1;i<=n;i++){
int t=lower_bound(b+1,b+m+1,a[i])-b;
Root[++Top]->num=1;
Insert(Root[Top],Root[Top-1],t,1,m);
}
for(int i=1;i<=M;i++){
scanf("%d %d %d",&L,&R,&k);
int t=Query_Rankk(Root[R],Root[L-1],k);
printf("%d\n",b[t]);
}
return 0;
}
来源:CSDN
作者:lycccccccc
链接:https://blog.csdn.net/lycccccccc/article/details/104597922