【主席树】poj 2104 K-th Number

这道题目要求我们求出一个区间内的第k大,静态的

首先可以考虑建立n棵权值线段树,第i棵线段树[a,b]记录数组1-i的区间中,数字a-b一共出现了多少次,也就是说这棵线段树的叶子节点的位置表示的是这个数字在1-i这个区间出现的次数

如果要求区间[a,b]内的第k大,我们可以利用前缀和的思想,将第b棵线段树和第a-1棵线段树相减,那么就可以得到了[a,b]这个区间内的权值线段树,然后就可以按照线段树的思想,去query第k的位置就可以了

然而,我们需要开n棵线段树,显然空间消耗过大

我们通过观察线段树,可以发现每两个相邻的线段树有很大的重复性,考虑是否在建新的一棵树的时候能否利用之前的位置,减少点的建立,合并在一棵大树上

考虑到每次建下一棵树的时候,实际上只加上了一个数值,也就是对于这个数值对应的叶子节点要有修改(+1),然后向上更新修改即可,也就是每次需要新建lgN个点,然后其余的位置就可以和上一次的位置相连

到此,树的建立就完成了

然后就是查询操作了,我们可以进行和线段树上查询基本一样的操作了,就是每次的数量用前缀和的差进行计算了

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int tot,n,m,sum[maxn<<5],has[maxn],a[maxn],fa[maxn],l[maxn<<5],r[maxn<<5];
int build(int L,int R)
{
	int rt=++tot;
	sum[rt]=0;
	if(L<R)
	{
		int mid=(L+R)>>1;
		l[rt]=build(L,mid);
		r[rt]=build(mid+1,R);
	}
	return rt;
}
int update(int pre,int L,int R ,int x)
{
	int rt=++tot;
	l[rt]=l[pre]; r[rt]=r[pre]; sum[rt]=sum[pre]+1;
	if(L<R)
	{
		int mid=(L+R)>>1;
		if(x<=mid)
			l[rt]=update(l[pre],L,mid,x);
		else r[rt]=update(r[pre],mid+1,R,x);
	}
	return rt;
}
int query(int left ,int right ,int L,int R,int x)
{
	if(L>=R) return L;
	int mid=(L+R)>>1;
	int num=sum[l[right]]-sum[l[left]];
	if(num>=x)
		return query(l[left],l[right],L,mid,x);
	else return query(r[left],r[right],mid+1,R,x-num);
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout); 
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),has[i]=a[i];
	sort(has+1,has+n+1);
	int d=unique(has+1,has+n+1)-has-1;
	fa[0]=build(1,d);
	for(int i=1;i<=n;i++)
	{
		int x=lower_bound(has+1,has+d+1,a[i])-has;
		fa[i]=update(fa[i-1],1,d,x);
	}
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		int t=query(fa[x-1],fa[y],1,d,z);
		printf("%d\n",has[t]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/andyc_03/article/details/107720771