【归并树】soj 3010 kth number

题意:

给n个数,q次询问。每次求出指定区间(x,y)之间排序后第k个数。

考虑到n的范围是10^5,m的范围是5*10^4.常规方法必然超时。考虑使用树结构来存储每一段区间(按线段树建树方式划分和查找,提高效率)。总的来讲,就是把数组用归并排序,因为我们希望得到每个二区间的原数与有序数。再用线段树存下这个归并排序的过程(称其为归并树)。对于每一次询问,二分枚举1-n中的数来检验是否是该区间的第k个数。不用担心一种情况:枚举到m,且m满足在该区间有k-1个数比他小,但m却不是这个区间内的值。这个问题,在二分过程中可以很好避免。自己想咯。

代码:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<string.h>
#include<string>
#include<map>
using namespace std;
const int N = 100000+10;
int n,q,rk;
struct node
{
	int left,right;
	int depth;
};
node seg_tree[N*4];
int a[N],aa[N];
int b[100][N];
		
void creattree(int l,int r,int rt,int depth)
{
	seg_tree[rt].left = l;
	seg_tree[rt].right = r;
	seg_tree[rt].depth = depth;
	if(l==r)	
	{
		b[depth][l]=a[l];
		return;
	}
	int mid = (l+r)/2;
	creattree(l,mid,rt<<1,depth+1);
	creattree(mid+1,r,rt<<1|1,depth+1);
	int ps=l,qs=mid+1;
	int k=l;
	while(ps<=mid || qs<=r)
	{
		if((ps>mid)||(qs<=r && a[ps]>a[qs]))
			b[depth][k++] = a[qs++];
		else 
			b[depth][k++] = a[ps++];
	}
	for(int i=l;i<=r;i++)
		a[i]=b[depth][i];
}

int query(int l,int r,int rt,int key)
{
	if(l==seg_tree[rt].left && r==seg_tree[rt].right)
	{
		
		int dep = seg_tree[rt].depth;
		int res = 0;
		int L = seg_tree[rt].left,R=seg_tree[rt].right;
		res = upper_bound(&b[dep][L],&b[dep][R]+1,key)-&b[dep][L];
		return res;
	}
	int mid = (seg_tree[rt].left+seg_tree[rt].right)/2;
	if(mid>=r) 
		return query(l,r,rt<<1,key);
	else if(mid+1<=l) 
		return query(l,r,rt<<1|1,key);
	else 
		return query(l,mid,rt<<1,key) + query(mid+1,r,rt<<1|1,key);
}

int main()
{
	while(scanf("%d%d",&n,&q)==2)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
		creattree(1,n,1,1);
		while(q--)
		{
			int x,y;
			scanf("%d%d%d",&x,&y,&rk);
			int L=1,R=n,mid;
			int ans=R;
			while(L<=R)
			{
				mid = (L+R)/2;
				int kth = query(x,y,1,a[mid]);
				if(kth>=rk)
					R=mid-1;
				else 
					L=mid+1;
			}
			ans = a[L];
			printf("%d\n",ans);
		}
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值