主席树

天大地大妈咪最大 提交于 2020-03-02 00:44:39

主席树是什么
先思考这样一个问题,给定一段序列,和若干个询问,每次查询区间[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;
 } 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!