主席树基础介绍

自己是没有写主席树教程的想法啦,毕竟网上那么多资料,写那些东西的人的水平比我不知道高到哪里去了,而且主席树的用法不仅仅是区间第k大,好多用法我这个弱菜根本就不知道,哪有什么写教程的资格,不过之前队友问过我对主席树的理解(帮助他入门)。当时自己根据自己的想法写了点东西,就想不如把这些东西放在博客上好了,也是逼自己以后多做一些主席树的题目。

先把坑挖在这里了啊……

放一个入门题目的地址: http://poj.org/problem?id=2104
题意:给你一个含n个数的序列,接下来有m个询问,每次询问你原序列[L,R]区间上第k大的数是哪一个

思路:就是主席树的裸题,所以我直接下构造方法好了
主席树又叫可持久化线段树,可持久化的意思大致是指这棵线段树可以存下自己以前的状态,也就是说即使我们更改了这棵线段树,更改前的内容还是被存在某个地方。

首先我们要有一个普通的线段树(每个叶子节点代表第几大的数)。
接下来开始从原序列最后一个点开始,用序列上的点一个一个更新线段树。更新的方法就是走到该点在原线段树处的位置,并把更新时的路径作为新线段树的一部分记录下来。
所以更新时路径上的每个点都要作为线段树的一个新节点覆盖在原来的节点上(请自行想象一个立体的图形…………一棵好好的线段树,某节点上面叠了好多节点的样子),然后这些新节点的左右儿子,一个是在更新路径上的新节点,另外一个就是它所覆盖的原节点的左(或者是右)儿子。
这里还有一个值val,或者c或者其他什么的,入门时可以简单理解为某节点和被它盖住的几个节点所处路径被走过了几次。
最后是查询操作,查找某区间排序后的第K个点,就是像二分查找一样在给定区间找第K个点的,所给区间的右端点所处线段树为底线,因为它是更新时最先出现的点,(建树后的更新是从右往左更新的,上文有提到)左端点为上界,因为它是最后出现的点(再想想一个立体的图形 …………一棵线段树的一个节点被一个节点盖着)。然后如果上界被走过的次数减去下界的次数(这个次数就是左儿子代表区间存的数字的个数)比K大的话,那么第K大的数就是在左儿子所代表区间里的第k大数,反之在右儿子所代表区间的第l大数,l=k-左儿子代表区间存的数字的个数。当所给区间被我们缩小到一个数时,这个数就是答案了。

说的可能不好,详见代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x)) 
#define MP(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
typedef long long LL;
inline void fre1(){freopen("input.txt","r",stdin);/*freopen("output.txt","w",stdout);*/}
inline void fre2(){fclose(stdin);/*fclose(stdout);*/}
const int MAXN=100000+10;
const double EPS=1e-8;
int val[MAXN*30],lson[MAXN*30],rson[MAXN*30];
int tot;
int a[MAXN],b[MAXN],idx[MAXN];
int n,m;

int build(int l,int r){
	int root=tot++;
	val[root]=0;
	if(l!=r){
		int mid=(l+r)>>1;
		lson[root]=build(l,mid);
		rson[root]=build(mid+1,r);
	}
	return root;
}
int update(int root,int pos,int V){
	int newroot=tot++,ret=newroot;
	val[newroot]=val[root]+V;
	int l=1,r=m,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(pos<=mid){
			lson[newroot]=tot++;rson[newroot]=rson[root];
			newroot=lson[newroot];
			root=lson[root];
			r=mid;
		}
		else{
			lson[newroot]=lson[root];rson[newroot]=tot++;
			newroot=rson[newroot];
			root=rson[root];
			l=mid+1;
		}
		val[newroot]=val[root]+V;
	}
	return ret;
}
int query(int left_root,int right_root,int k){
	int l=1,r=m,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(val[lson[left_root]]-val[lson[right_root]]>=k){
			left_root=lson[left_root];right_root=lson[right_root];
			r=mid;
		}
		else{
			k-=val[lson[left_root]]-val[lson[right_root]];
			left_root=rson[left_root];right_root=rson[right_root];
			l=mid+1;
		}
	}
	return l;
}

int main()
{
	int q;
	while(scanf("%d%d",&n,&q)==2){
		tot=0;
		for(int i=1;i<=n;++i) scanf("%d",a+i),b[i]=a[i];
		sort(b+1,b+1+n);
		m=unique(b+1,b+1+n)-b-1;
		idx[n+1]=build(1,m);
		for(int i=n;i;--i){
			int pos=lower_bound(b+1,b+1+m,a[i])-b;
			idx[i]=update(idx[i+1],pos,1);
		}
		while(q--){
			int l,r,k;
			scanf("%d%d%d",&l,&r,&k);
			printf("%d\n",b[query(idx[l],idx[r+1],k)]);
		}
	}
	return 0;
}

自己还写过另外一个基础主席树题目的题解,欢迎有兴趣的人看一看: http://blog.csdn.net/dpppBR/article/details/52143543

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值