HDU - 2665 Kth number 主席树/可持久化权值线段树

题意 

  给一个数列,一些询问,问$[l,r]$中第$K$大的元素是哪一个

题解:

  写法很多,主席树是最常用的一种之一

  除此之外有:划分树,莫队分块,平衡树等

  主席树的定义其实挺模糊,

  一般认为就是可持久化线段树/函数式线段树

  但是最初被创造用来求区间第$K$大的时候,是特指可持久化的权值线段树

  主席树的特点就是 $[l,r]$的信息可以通过第$r$颗树的减去第$l$颗树得到

  具体思路就是:

  将数列的值进行排序,去重

  对于原序列的每一个值,依次插入权值线段树,将它所在排序位置权值+1

  在线段树中,每一次插入实际上都会开一颗节点为$log(n)$新的线段树,树根保存为$rt_i$,存有$[1,i]$的区间信息

  然后对于每次询问$[l,r]$,$tree_r - tree_{l-1}$的第$K$个数字即为答案

#include <bits/stdc++.h>
#define nd seg[now]
#define ndp seg[pre]
#define mid ((s+t)>>1)
using namespace std;
const int maxn=1e5+10;
vector<int>pos;
int casn,n,m,k;
int num[maxn],rt[maxn],size;
struct node{
	int l,r,sum;
}seg[maxn*20];
void maketree(int s=1,int t=n,int &now=rt[0]){
	now=++size;nd={s,t,0};
	if(s==t) return ;
	maketree(s,mid,nd.l);maketree(mid+1,t,nd.r);
}
void update(int &now,int pre,int k,int s=1,int t=n){
	now=++size;nd=ndp,nd.sum++;
	if(s==t) return ;
	if(k<=mid)update(nd.l,ndp.l,k,s,mid);
	else update(nd.r,ndp.r,k,mid+1,t);
}
int query(int now,int pre,int k,int s=1,int t=n){
	if(s==t) return s;
	int sum=seg[ndp.l].sum-seg[nd.l].sum;
	if(k<=sum) return query(nd.l,ndp.l,k,s,mid);
	else return query(nd.r,ndp.r,k-sum,mid+1,t);
}
int main(){
	scanf("%d",&casn);
	while(casn--){
		scanf("%d%d",&k,&m);
		pos.clear();
		size=0;
		for(int i=1;i<=k;i++){
			scanf("%d",num+i);
			pos.push_back(num[i]);
		}
		sort(pos.begin(),pos.end());
		pos.erase(unique(pos.begin(),pos.end()),pos.end());
		n=pos.size();
		maketree(1,n,rt[0]);
		for(int i=1;i<=k;i++){
			int id=lower_bound(pos.begin(),pos.end(),num[i])-pos.begin()+1;
			update(rt[i],rt[i-1],id);
		}
		while(m--){
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			printf("%d\n",pos[query(rt[a-1],rt[b],c)-1]);
		}
	}
  return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/nervendnig/p/9197142.html