[FROM WOJ]#1903 第k大的数

#1903 第k大的数

主席树模版题

题面
你为Macrohard公司的数据结构部门工作,你的工作是重新写一个数据结构,这个数据结构能快速地找到一段数列中第k大的数。

就是说,给定一个整数数列a[1…n],其中每个元素都不相同,你的程序要能回答一组格式为Q (i , j , k)的查询,Q(i, j ,k)的意思是“在a[i…j]中第k大的数是多少?”

例如令 a = {1, 5, 2, 6, 3, 7, 4},查询格式为Q (2 , 5 , 3),数列段a[2…5] = {5, 2, 6, 3},第3大的数是5,所以答案是5。

输入
文件第一行包括一个正整数n,代表数列的总长度,还有一个数m,代表有m个查询。 n,m满足:1≤n≤100 000, 1≤m≤5 000 第二行有n个数,代表数列的元素,所有数都不相同,而且不会超过109 接下来有m行,每行三个整数i , j , k,代表一次查询, i , j , k满足1≤i≤j≤n, 1≤k≤j − i + 1

输出
输出每个查询的答案,用换行符隔开

样例输入
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

样例输出
5
6
3

SOL
对于求第k大问题,我们可能会想用线段树来维护,方法类似于前缀和,但是想求任意区间的第k大,线段树要造N棵,容易MLE。ssldw说也可以用分块来做,但是写得丑就会TLE……
容易看出这道题满足区间减法——求区间[L,R]的第k大,就是要找出[L,R]中有k个数大于x,若[1,L-1]中有s1个数大于x,[1,R]中有s2个数大于x,那么[L,R]中就会有s2-s1个数大于x。
于是就想到了主席树(?)。
主席树的特点就在于她只用增加改变过的节点,历史版本中未改变的节点会被直接复制的当前版本的树上,不会增加节点。满足区间减法的题常常可以考虑主席树。

代码:

#include<bits/stdc++.h>
#define N 100100
#define M 100100
int n,m;
int a[N],h[N];
using namespace std;
inline int rd(){
	int w=1,data=0;static char ch='0';
	ch=getchar();
	while((ch!='-')&&(!isdigit(ch)))ch=getchar();
	if(ch=='-')w=-1,ch=getchar();
	while(isdigit(ch))data=(data<<1)+(data<<3)+ch-'0',ch=getchar();
	return data*w;
}
inline void write(int x){
	if(x<0){putchar('-');x=-x;}
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
int root,tot;
struct tree{
	int sum,l,r,id;
}t[N<<5];
inline int build(int l,int r){
	++tot;int root=tot;t[root].sum=0;
	if(l<r){
		int mid=l+r>>1;
		t[root].l=build(l,mid);
		t[root].r=build(mid+1,r);
	}
	return root;
}
inline int update(int p,int ql,int qr,int pre){
	++tot;int root=tot;
	t[root].l=t[pre].l,t[root].r=t[pre].r,t[root].sum=t[pre].sum+1;//如果没有更改,就将前一棵树的信息全部复制过来 
	if(ql<qr){
		int mid=ql+qr>>1;
		if(p<=mid)t[root].l=update(p,ql,mid,t[pre].l);//有更改则对当前版本的根节点做改动 
		else t[root].r=update(p,mid+1,qr,t[pre].r);
	}
	return root;
}
inline int query(int ql,int qr,int u,int v,int k){
	if(ql>=qr)return ql;
	int mid=ql+qr>>1;
	int num=t[t[v].l].sum-t[t[u].l].sum;//在左边搜左边,在右边搜右边 
	if(num>=k)return query(ql,mid,t[u].l,t[v].l,k);
	else return query(mid+1,qr,t[u].r,t[v].r,k-num);
}
int main(){
	int n=rd(),m=rd();
	for(int register i=1;i<=n;i++){
		a[i]=rd();
		h[i]=a[i];//hash
	}
	sort(h+1,h+n+1);
	int nn=unique(h+1,h+n+1)-h-1;
	t[0].id=build(1,nn);
	for(int register i=1;i<=n;i++){
		int pos=lower_bound(h+1,h+n+1,a[i])-h;
		t[i].id=update(pos,1,nn,t[i-1].id);
	}
	while(m--){
		int register u=rd(),v=rd(),k=rd();
		int pos=(query(1,nn,t[u-1].id,t[v].id,k));
		write(h[pos]);putchar('\n');
	}return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值