K-th Number 主席树基础题,两种模板(详细注释)

在此之前   了解 权值线段树   &&&   主席树

K-th Number 

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?" 
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given. 
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

模板1

#include<cstdio>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e5+5;
int cnt,root[maxn],a[maxn];
//root[i] 第i课线段树根节点的位置 
//cnt 用作开辟新的树节点。
struct node{
	int l,r;//左右儿子结点编号,因为不满足2*rt规律 
	int sum;//代表(l,r)区间上和是多少 
}T[maxn*40];   //T[]表示每一棵树 
vector<int> v;
int getid(int x){   //得到下标 
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
/** update函数介绍
l,r 代表线段树递归的区间,x代表前一棵树的节点位置,y是后面的节点位置。
在递归的过程中,将需要修改的树节点复制到新开辟节点,改变自己的sum,
也就是自加1,顺便改变上一个的孩子节点
所以传参是引用传参;//线段树区间统计,sum代表在这个区间数的个数。
*/
//update(1,n,root[i],root[i-1],getid(a[i]));
void update(int l,int r,int &x,int y,int p){      //建树的过程 
	T[++cnt] = T[y];//左右son和sum都先连接 
	T[cnt].sum++;
	x = cnt;
	if(l==r) return ;
	int m = (l+r)>>1;
	if(m>=p) update(l,m,T[x].l,T[y].l,p);
	else update(m+1,r,T[x].r,T[y].r,p);
}
/**  query函数介绍
因为是查找第K小,所以在查找时候只需要看左边孩子节点,
两棵线段树sum做差,便得到这个区间的值
比如 root[R]-root[L-1] ,则代表区间 [L,R] 的数的统计
所以 S=(R线段树左孩子的sum)-(L-1线段树左孩子的sum)
 如果 S>=K(第K小),所以第K小肯定在左儿子节点,
否则,右节点,并且在右边区间再找剩下的 K-S,即可。
*/
//query(1,n,root[l],root[r],k); 
int query(int l,int r,int x,int y,int k){   //x为老树,y为新树 
	if(l == r) return l;
	int m = (l+r)>>1;
	int sum = T[T[y].l].sum - T[T[x].l].sum;
	if(k<=sum) return query(l,m,T[x].l,T[y].l,k);
	else return query(m+1,r,T[x].r,T[y].r,k-sum);
}
int main(){
	int n,m,x,y,k;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		v.push_back(a[i]);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());//去重
	for(int i=1;i<=n;i++) 
		update(1,n,root[i],root[i-1],getid(a[i]));
	for(int i=1;i<=m;i++) {	
		scanf("%d%d%d",&x,&y,&k);	
		printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);//可能因为离散化的缘故,所以要-1 
	}	
	return 0;
}

模板2

#include<cstdio> 
#include<cstring>
using namespace std;
int root[100010],pr[100010],num=0;
//rt[i] 第i课线段树根节点的位置 
//num 用作开辟新的树节点。
struct node1
{
	int l,r;//左右儿子结点编号,因为不满足2*rt规律 
	int sum;//代表(l,r)区间上和是多少 (即该节点的权值) 
}f[3000010]; //f[]表示每一棵树 
struct node2
{
	int id,s;
}a[100010];  //原数组a[],写成结构体方便离散化
void qsort(int l,int r)  //离散化a[] 
{
	int x=l,y=r,mid=a[(l+r)/2].s;
	while(x<=y)
	{
		while(a[x].s<mid) x++;
		while(a[y].s>mid) y--;
		if(x<=y) a[0]=a[x],a[x]=a[y],a[y]=a[0],x++,y--;
	}
	if(x<r) qsort(x,r);
	if(y>l) qsort(l,y);
}
void make(int v,int l,int r)
{
	if(l==r) 
	{
		f[v].sum=0;
		if(v>num) num=v;//先记录空线段树所用的节点数。
		return;
	}
	else
	{
		int mid=(l+r)/2;
		f[v].l=v*2,f[v].r=v*2+1;
		make(v*2,l,mid);
		make(v*2+1,mid+1,r);
	}
}
void add(int v,int v1,int l,int r,int x)
{
	if(l==r)
	{
		f[v1].sum++;
		return;
	}
	else
	{
		int mid=(l+r)/2;
		if(x<=mid)//要修改的数在左子树中。
		{
			f[v1].l=++num;//该节点的左儿子为新建节点。
			f[v1].r=f[v].r;//右儿子为上一次修改后的右儿子。
			add(f[v].l,f[v1].l,l,mid,x);
		}
		else//要修改的数在右子树中。
		{
			f[v1].l=f[v].l;//左儿子为上一次修改后的左儿子。
			f[v1].r=++num;//该节点的右儿子为新建节点。
			add(f[v].r,f[v1].r,mid+1,r,x);
		}
		f[v1].sum=f[f[v1].l].sum+f[f[v1].r].sum;//当前的总和为左右节点的总和之和。
	}
}
int find(int v,int v1,int l,int r,int k)
{
	if(l==r) return a[l].s;
	else 
	{
		int mid=(l+r)/2,s1=f[f[v1].l].sum-f[f[v].l].sum,s2=f[f[v1].r].sum-f[f[v].r].sum;//s1为第x~y个数中范围为[l,mid]的个数,s2为第x~y个数中范围为(mid,r]的个数。
		if(s1>=k) return find(f[v].l,f[v1].l,l,mid,k);
		else return find(f[v].r,f[v1].r,mid+1,r,k-s1);
	}
}

int main(){
	int n,m,x,y,k;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i].s);
		a[i].id=i;
	}
	qsort(1,n);          //离 
	for(int i=1;i<=n;i++)//散 
		pr[a[i].id]=i;   //化
	root[0]=1;
	make(1,1,n);//先建立一棵为空的权值线段树。
	for(int i=1;i<=n;i++)  //建立全部的树 
	{
		root[i]=++num;
		add(root[i-1],root[i],1,n,pr[i]);//从上一个根和当前的根一起往下递归。
	}
	for(int i=1;i<=m;i++)
	{	
		scanf("%d%d%d",&x,&y,&k);
		printf("%d\n",find(root[x-1],root[y],1,n,k));
	}
	return 0; 
} 

阿里云建站—为企业提供互联网“快”服务

2020年因为一场突如其来的疫情,不少企业受到了严重冲击,疫情的冲击力对传统“纯线

下”行业的危害最大,互联网女皇玛丽·米克(MaryMeeker)4月17日发布了著名的年度互

联网趋势报告,报告中指出:拥有强大的互联网线上线下融合能力的企业在疫情中的表现最好,

线上线下融合的趋势已经存在一段时间了,但是疫情让这种需求变得更加的迫切。

如果你的企业完全依附于传统的、纯线下的经验模式,那么在2020年你将“必死无疑”,

一场巨大的,前所未有的互联网革命已经到来!

阿里云建站为了助力各行各业复工复产,疫情期间“马不停蹄”为数以万计的企业快速完成

建站,为他们朝着“线上线下融合”或者“纯线上”的互联网经营模式迈进,打下了坚实的基础。

“云·速成美站”模板建站1天就能上线,不懂技术没关系,打字就能建网站。千套网站模

板免费提供,百元就能建官网,一价全包,无任何隐形消费。

“云·企业官网”定制建站1周就能上线,高端量身定制建站,千元建官网无需自己动手,

建站专家1对1网站策划及设计,专业省心之选。

疫情,是一场大浪淘沙,每一次危机背后都隐藏着机会,危机越大,机会也越大。大环境

已经改变,如果你不努力不去改变来迎合这个大环境,那你必将被淘汰。

阿里云助力企业建站,优惠多多,福利多多,详情请点击如下链接

红包+折扣,阿里云上云大礼包! https://www.aliyun.com/minisite/goods?userCode=cakfq1st

猜你喜欢

转载自blog.csdn.net/weixin_44075041/article/details/98877060