主席树经典题目 区间k大值 poj2104

29 篇文章 0 订阅
10 篇文章 0 订阅

年前就开始写的一个题。。。一直没调。。

中间贡献无数wa原因是忘了函数式的数据结构要开30倍空间。。。。


至今对主席树的理解还是不算很深

主席树大概就是对每个前缀或者后缀都单独的维护一颗线段树,然后由于主席树的可加减性,可以减出来区间的情况


用丽杰姐姐的话来说就是划分树已经成为时代的眼泪。。。。


后来稍微懂了。。。

大概就是首先闹一个 rank

这样我们就知道每个数在现在区间的排名了,相当于是一个离散的过程,当然有重复就麻烦了

然后我们每个节点保存的 就是一个数字而已,大概意思是这段范围内一共出现了几次

但是我们并不保留具体的值


因为主席树是可以减的,所以一直减,一直到叶节点,就显而易见了。。。

这是主席树的精髓。。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define MAX 100010

using namespace std;

int t[MAX*30];
int n,c[MAX],a[MAX],child[MAX*30][2],size,tree[MAX*30];
int tot=0,m;

bool cmp(int a1,int a2)
{
	return a1<a2;
}

void before()
{
	for(int i=1;i<=n;i++)
		c[i]=a[i];
	sort(c+1,c+1+n,cmp);
	size=unique(c+1,c+1+n)-(c+1);
}

int build(int la,int ra)
{
	int root=tot++;
	tree[root]=0;
	if(la!=ra)
	{
		int mid=(la+ra)/2;
		 child[root][0]=build(la,mid);
		 child[root][1]=build(mid+1,ra);
	}
	return root;
}

int find(int x)
{
	return lower_bound(c+1,c+1+size,x)-c;
}

int update(int root,int pos,int value)
{
	int newnode=tot++,tmp=newnode;
	tree[newnode]=tree[root]+value;
	int l=1,r=size;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(pos<=mid)
		{
			child[newnode][0]=tot++;
			child[newnode][1]=child[root][1];
			newnode=child[newnode][0];
			root=child[root][0];
			r=mid;
		}
		else
		{
			child[newnode][1]=tot++;
			child[newnode][0]=child[root][0];
			newnode=child[newnode][1];
			root=child[root][1];
			l=mid+1;
		}
		tree[newnode]=tree[root]+value;
	}
	return tmp;
}

int ask(int l,int r,int k)
{
	int L=1,R=size;
	while(L<R)
	{
		int mid=(L+R)>>1;
		if(tree[child[l][0]]-tree[child[r][0]]>=k)
		{
			R=mid;
			l=child[l][0];
			r=child[r][0];
		}
		else
		{
			L=mid+1;
			k-=tree[child[l][0]]-tree[child[r][0]];
			l=child[l][1];
			r=child[r][1];
		}
	}
	return L;
}

int main()
{
	while(scanf("%d%d",&n,&m)==2)
	{
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	before();
	tot=0;
	t[n+1]=build(1,size);
	for(int i=n;i>=1;i--)
	{
		int pos=find(a[i]);
		t[i]=update(t[i+1],pos,1);
	}
	while(m--)
	{
		int l,r,k;
		scanf("%d %d %d",&l,&r,&k);
		printf("%d\n",c[ask(t[l],t[r+1],k)]);
	}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值