结合poj 2104,对于简单的归并树谈谈个人的理解和做法

题目:poj 2104

大意:给你一个长度为n的数列,每次查询数列下标为[i,j]区间里面升序排序后第k大的数

(我刚刚加入学校集训队暑假acm集训的时候遇到这题直接头铁sort。。。。现在想起来还真是天真)

由此引出归并树(其实这一题用主席树更快,点击传送!

首先,区间升序排序第k大需要做一个小小的转换,不妨设那个数就是aim,就是区间内比aim小的<k个,小于等于aim的>=k个。(区间中可能不止一个aim),转换下来之后这道题可以转化为数区间内比aim小的数的个数。那么我怎么知道区间内那个aim是什么呢直接告诉了还做个屁。。),那么就对原来的数组排序,二分查找,利用每个数所查询到的值来判断。

那么归并树的做法是怎么样的呢,借用《挑战程序设计竞赛》的一张图片

 

大体做法如上图所示(原谅我字丑)

实际上,由于区间内的aim不止一个,很可能有多个

lower_bound 自行体会。。。。

最后放上AC代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=100000*4+5;
vector<int> tree[maxn];
int a[100005];
void build_tree(int l,int r,int index){
	if(l==r){
		tree[index].push_back(a[l]);
		return;
	}
	int mid=(l+r)>>1, lson=index<<1, rson=index<<1|1;
	build_tree(l,mid,lson);
	build_tree(mid+1,r,rson);
	tree[index].resize(r-l+1); //没这句merge会炸 
	merge(tree[lson].begin(),tree[lson].end(),tree[rson].begin(),tree[rson].end(),tree[index].begin());	
}
int query(int q_l,int q_r,int L,int R,int index,int aim){
	if(q_l>R||q_r<L) return 0; //没啥关系的区间返回0 
	else if(q_l<=L&&q_r>=R) return upper_bound(tree[index].begin(),tree[index].end(),aim)-tree[index].begin();
	else{ //有区间没在分好的地方那就继续二分下去 
		int res=0,mid=(L+R)>>1;
		res+=query(q_l,q_r,L,mid,index<<1,aim);
		res+=query(q_l,q_r,mid+1,R,index<<1|1,aim);
		return res;
	} 	
}
int main(){
	int n,q; cin>>n>>q;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	build_tree(1,n,1);
	sort(a+1,a+1+n);
	while(q--){ 
		int l,r,k; 
		scanf("%d %d %d",&l,&r,&k);
		int ll=1,rr=n;
		while(ll<rr){ //二分查找那个值到底是多少 
			int mid=(ll+rr)>>1;
			int res=query(l,r,1,n,1,a[mid]);
			if(res>=k){ 
				rr=mid;
			}
			else ll=mid+1;  
		}
		cout<<a[rr]<<endl;
	}
	return 0;
} 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值