主席树初步

主席树是一个很神奇的东西,效率次于划分树。

关于思路,还是用图片来展示吧:


附上poj2104代码:

#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<cmath>
#define M 100000+5
using namespace std;
/*
以poj2104求[l,r]第k大的数为例探究主席树 
*/
struct node
{
	int l,r,size;
	node(){l=r=size=0;
	}
}T[20*M];
int root[M];//记录第i个子树的根
int tot;//各点标号 
int m;
int a[M];//读入数组
int t[M];//离散数组 1,5,7,9等数字离散后 t[1]=1 t[2]=5 t[3]=7
int pos(int x)
{
	return lower_bound(t+1,t+m+1,x)-t;//寻找在离散数组中的位置 
	/*int l=1,r=m;
	while(l<r)
	{
		int mid=(l+r)/2;
		if(x<=t[mid])r=mid;
		else l=mid+1;
	}
	return l;*/
}
int n;//n个数
int q;//q个询问 
bool cmp(int x,int y)
{
	return x<y;
}
int build(int l,int r)
{
	int rt=tot++;
	T[rt].size=0;
	if(l==r)return rt;
	int mid=(l+r)/2;
	T[rt].l=build(l,mid);
	T[rt].r=build(mid+1,r);
	return rt;
}//建树 
int updata(int u,int l,int r,int x,int v)/*新建节点和u
进行比较(同一级),在区间[l,r]中把x的位置的size加上v 
*/ 
{
	int rt=tot++;
	T[rt]=T[u];//信息传递
	T[rt].size+=v;//加上size
	//要注意的是构造新节点的时候我们是从上往下构造的,
    //而不是回溯的时候
	if(l==r)return rt;
	int mid=(l+r)/2;
	if(x<=mid)//要修改的信息在左儿子 
	{
		T[rt].l=updata(T[u].l,l,mid,x,v);
	} 
	else T[rt].r=updata(T[u].r,mid+1,r,x,v);
	return rt;
}
int query(int i,int j,int l,int r,int k)//i,j两棵树对比 
{
	if(l==r)return l;
	int cha=T[T[j].l].size-T[T[i].l].size;
	int mid=(l+r)/2;
	if(cha>=k)return query(T[i].l,T[j].l,l,mid,k);
	else return query(T[i].r,T[j].r,mid+1,r,k-cha);
}
int main()
{
	while(scanf("%d%d",&n,&q)!=EOF)
	{
		for(int i=1;i<=n;i++)
    	{
		    scanf("%d",&a[i]);
	    	t[i]=a[i];
    	}
    	sort(t+1,t+n+1,cmp);
    	m=unique(t+1,t+n+1)-t-1;
    	m++;t[m]=0x3f3f3f3f; 
    	tot=0;
    	root[0]=build(1,m);//初始化树
    	for(int i=1;i<=n;i++)//每个点的建树通过Logn的上传修改 
	    {
		    int k=pos(a[i]);//k是a[i]这个数对应在离散后的数中的位置 
		    root[i]=updata(root[i-1],1,m,k,1);//传递修改 ,参数含义见上 
    	} 
	    //处理好之后剩下的是查询
	    for(int i=1;i<=q;i++)
    	{
	    	int l,r,k;//寻找[l,r]区间里的第k小的元素 
		    scanf("%d%d%d",&l,&r,&k);
	    	printf("%d\n",t[query(root[l-1],root[r],1,m,k)]);//查询[l,r]里面第k小的元素 
	    } 
	}
	return 0;
}


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值