luoguP3834主席树(可持久化线段树)模板

题目背景

这是个非常经典的主席树入门题——静态区间第 k 小。
数据已经过加强,请使用主席树。同时请注意常数优化。

题目描述
如题,给定 n 个整数构成的序列 a,将对于指定的闭区间 [l, r] 查询其区间内的第 k 小值。

输入格式
第一行包含两个整数,分别表示序列的长度 n 和查询的个数 m。
第二行包含 n 个整数,第 ii 个整数表示序列的第 i 个元素 a[i]。
接下来 m 行每行包含三个整数 l,r,k表示查询区间[l, r]内的第 k 小值。

输出格式
对于每次询问,输出一行一个整数表示答案。

输入输出样例
输入 #1复制
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
输出 #1复制
6405
15770
26287
25957
26287

第一次查询为[2, 2] 区间内的第一小值,即为 6405。
第二次查询为[3, 4]区间内的第一小值,即为 15770。
第三次查询为[4, 5]区间内的第一小值,即为 26287。
第四次查询为[1, 2]区间内的第二小值,即为 25957。
第五次查询为[4, 4]区间内的第一小值,即为 26287。
数据规模与约定
对于 20% 的数据,满足 1≤n,m≤10。
对于 50% 的数据,满足 1≤n,m≤1000 。
对于 80% 的数据,满足 1≤n,m≤100000 。
对于 100% 的数据,满足 1≤n,m≤200000,-1e9<=a[i]<=1e9。

思路

待补充…

代码

	#include
	#define N 1000005
	#define re read()
	using namespace std;

	int n,m,tot,len;
	int a[N],b[N],root[N];
	struct Tree{
	int l,r,val,num;}tree[16*N];

	inline int re{
		int i=0,f=1;char ch;
		while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
		while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch^48);ch=getchar();}
		return i*f;
	}

	inline void Init()
	{
		sort(b+1,b+n+1);
		len=unique(b+1,b+n+1)-(b+1);
		for(int i=1;i<=n;i++)
		a[i]=lower_bound(b+1,b+len+1,a[i])-b;
		return;
	}

	inline void build(int y,int &x,int l,int r,int val)
	{
		x=++tot;
		tree[x]=tree[y];
		tree[x].num++;
		if(l==r)return;
		int m=(l+r)>>1;
		if(val<=m) build(tree[y].l,tree[x].l,l,m,val);
		else build(tree[y].r,tree[x].r,m+1,r,val);
		return;
	}

	inline int Query(int x,int y,int l,int r,int k)
	{
		if(l==r)return l;
		int m=(l+r)>>1;
		int sum=tree[tree[x].l].num-tree[tree[y].l].num;
		if(k<=sum) return Query(tree[x].l,tree[y].l,l,m,k);
		else return Query(tree[x].r,tree[y].r,m+1,r,k-sum);
	}

	int main()
	{
		int x,y,z;
		n=re,m=re;
		for(int i=1;i<=n;i++)
		a[i]=b[i]=re;
		Init();
		for(int i=1;i<=n;i++)
		build(root[i-1],root[i],1,len,a[i]);
		for(int i=1;i<=m;i++)
		{
			x=re,y=re,z=re;
			printf("%d\n",b[Query(root[y],root[x-1],1,len,z)]);
		}
		return 0;
	}	

END

你可能感兴趣的:(主席树,权值线段树)