求静态区间第k小(主席树)

本文介绍了一种解决静态区间内查找第k小元素的问题,通过使用权值线段树和主席树,实现对区间查询的高效处理。适用于数据规模较大,查询频繁的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

静态区间第k小

题目描述

如题,给定NNN个正整数构成的序列,将对于指定的闭区间查询其区间内的第KKK小值。

输入格式

第一行包含两个正整数NNNMMM,分别表示序列的长度和查询的个数。
第二行包含NNN个正整数,表示这个序列各项的数字。
接下来MMM行每行包含三个整数 l,r,kl,r,kl,r,k,表示查询区间[l,r][l,r][l,r]内的第kkk小值。

输出格式

输出包含MMM行,每行111个正整数,依次表示每一次查询的结果

输入样例

555 555
259572595725957 640564056405 157701577015770 262872628726287 264652646526465
222 222 111
333 444 111
444 555 111
111 222 222
444 444 111

输出样例

640564056405
157701577015770
262872628726287
259572595725957
262872628726287

说明
数据范围:

对于20%的数据满足:1≤N,M≤101 \leq N, M \leq 101N,M10
对于50%的数据满足:1≤N,M≤1031 \leq N, M \leq 10^31N,M103
对于80%的数据满足:1≤N,M≤1051 \leq N, M \leq 10^51N,M105
对于100%的数据满足:1≤N,M≤2⋅1051 \leq N, M \leq 2\cdot 10^51N,M2105
对于数列中的所有数aia_iai,均满足−109≤ai≤109-{10}^9 \leq a_i \leq {10}^9109ai109

样例数据说明:

N=5N=5N=5,数列长度为555,数列从第一项开始依次为[25957,6405,15770,26287,26465][25957, 6405, 15770, 26287, 26465 ][25957,6405,15770,26287,26465]
第一次查询为[2,2][2,2][2,2]区间内的第一小值,即为640564056405
第二次查询为[3,4][3,4][3,4]区间内的第一小值,即为157701577015770
第三次查询为[4,5][4,5][4,5]区间内的第一小值,即为262872628726287
第四次查询为[1,2][1,2][1,2]区间内的第二小值,即为259572595725957
第五次查询为[4,4][4,4][4,4]区间内的第一小值,即为262872628726287


先简化问题,如果求的是多次询问同一段区间的第kkk小,我们可以怎么做?
用权值线段树对离散化后的数列进行统计,并在线段树上二分,若小于等于xxx的数的个数&gt;=k&gt;=k>=k,则答案一定&lt;=x&lt;=x<=x,否则答案一定&gt;x&gt;x>x
在权值线段树上二分就可以了。


现在我们要求任意一段区间的第kkk小,相当于要求出任意一段的权值线段树。有因为是静态的,很容易想到前缀和。我们对每一个前缀都开一棵建立在离散化数组上的权值线段树,若询问lll ~ rrr区间内的第kkk小,只要将第rrr棵线段树上的每个节点的数值−-lll棵线段树上的每个节点的数值就能生成目标线段树。

但是我们会发现一个问题,那就是将一整棵线段树赋值的时间与空间复杂度都是无法承受的。因此,主席树最最精妙的一点就是对于每一个时刻都保留前一个时刻树的整体,而只修改一条链数据。这样,预处理时间和空间复杂度都降到了O(logn)O(logn)O(logn)

代码

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 200005;
const int maxt = 7500000;
int n;
int a[maxn];
int read()
{
	char ch = getchar(); bool f = 1;
	while(ch < '0' || ch > '9') f &= ch != '-' , ch = getchar();
	int res = 0;
	while(ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48) , ch = getchar();
	return f ? res : -res;
}
void write(int x)
{
	if(x < 0) x = -x , putchar('-');
	int len = 0 , res[15];
	for(;x;res[++len] = x % 10 , x /= 10);
	for(int i = len;i >= 1;i--) putchar(res[i] + 48);
	if(!len) putchar('0');
}
struct inm{int ls , rs , cnt;};
struct CT
{
	private:
		int m;
		int b[maxn];
		int cnt;
		inm t[maxt]; int rt[maxn];
		void build(int &p , int l , int r)
		{
			p = ++cnt;
			if(l == r) return;
			int mid = l + r >> 1;
			build(t[p].ls , l , mid);
			build(t[p].rs , mid + 1 , r);
		}
		void update(int &p , int pre , int l , int r , int x)
		{
			p = ++cnt;
			t[p] = t[pre];
			t[p].cnt++;
			if(l == r) return;
			int mid = l + r >> 1;
			if(x <= mid) update(t[p].ls , t[pre].ls , l , mid , x);
			else update(t[p].rs , t[pre].rs , mid + 1 , r , x);
		}
		int kth(int p , int pre , int l , int r , int k)
		{
			if(l == r) return l;
			int mid = l + r >> 1 , num = t[t[p].ls].cnt - t[t[pre].ls].cnt;
			if(num >= k) return kth(t[p].ls , t[pre].ls , l , mid , k);
			else return kth(t[p].rs , t[pre].rs , mid + 1 , r , k - num);
		}
	public:
		void clear()
		{
			cnt = 0;
			for(int i = 1;i <= n;i++) rt[i] = 0;
		}
		void init()
		{
			for(int i = 1;i <= n;i++) b[i] = a[i];
			sort(b + 1 , b + n + 1);
			m = unique(b + 1 , b + n + 1) - b - 1;
			build(rt[0] , 1 , m);
			for(int i = 1;i <= n;i++)
			{
				int loc = lower_bound(b + 1 , b + m + 1 , a[i]) - b;
				update(rt[i] , rt[i - 1] , 1 , m , loc);
			}
		}
		int query(int l , int r , int k){return b[kth(rt[r] , rt[l - 1] , 1 , m , k)];}
}ct;
int main()
{
	n = read();
	int q = read();
	for(int i = 1;i <= n;i++) a[i] = read();
	ct.clear() , ct.init();
	while(q--)
	{
		int l = read() , r = read() , k = read();
		write(ct.query(l , r , k));
		putchar('\n');
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值