255. 第K小数

题目链接

255. 第K小数

给定长度为 \(N\) 的整数序列 \(A\), 下标为 \(1 \sim N\)
现在要执行 \(M\) 次操作,其中第 \(i\) 次操作为给出三个整数 \(l_{i}, r_{i}, k_{i}\) ,求 \(A\left[l_{i}\right], A\left[l_{i}+1\right], \ldots, A\left[r_{i}\right]\) (即 \(A\) 的下标区间 \(\left[l_{i}, r_{i}\right]\) )中第 \(k_{i}\) 小的数是多少。

输入格式

第一行包含两个整数 \(N\)\(M\)
第二行包含 \(N\) 个整数,表示整数序列 \(A_{\circ}\)
接下来 \(M\) 行,每行包含三个整数 \(l_{i}, r_{i}, k_{i}\) ,用以描述第 \(i\) 次操作。

输出格式

对于每次操作输出一个结果,表示在该次操作中,第 \(k\) 小的数的数值。
每个结果占一行。

数据范围

\( N \leq 10^{5}, M \leq 10^{4},|A[i]| \leq 10^{9} \)

输入样例:

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

输出样例:

5
6
3

解题思路

可持久化权值线段树,主席树

可持久化权值线段树的关键在于记录历史版本信息,而线段树如果不增加数的话结构是不变的,针对权值而不是下标建树,每插入一个数就复制上一个版本的信息,由于树的结构不变,可以直接复制上一个版本的根节点信息

本题值域过大,可以先离散化,树上的节点维护权值区间 \([l,r]\) 中出现的次数,单单考虑 \([1,r]\) 的情况,直接在以 \(root[r]\) 为根节点的线段树上求解即可,具体求解:考虑总体范围 \([1,n]\),如果 \([1,mid]\) 出现的个数不小于 \(k\),说明在左半边,即位于左子树,反之位于右子树。分别考虑以 \(root[L-1]\)\(root[R]\) 为根节点的线段树,考虑他们此时某个节点维护的某个某个区间 \([l,r]\),线段树维护的是在 \([l,r]\) 出现的数的个数,两者相减即为下标区间 \([L,R]\)\([l,r]\) 出现的数的个数,据此分治即可

  • 时间复杂度:\(O((n+m)\times logn)\)

代码

// Problem: 第K小数
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/257/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e5+5;
int n,m,a[N],root[N],idx;
vector<int> b;
struct T
{
	int l,r;
	int cnt;
}tr[N*4+17*N];
int find(int x)
{
	return lower_bound(b.begin(),b.end(),x)-b.begin();
}
int build(int l,int r)
{
	int p=++idx;
	if(l==r)return p;
	int mid=l+r>>1;
	tr[p].l=build(l,mid),tr[p].r=build(mid+1,r);
	return p;
}
int insert(int p,int l,int r,int x)
{
	int q=++idx;
	tr[q]=tr[p];
	if(l==r)
	{
		tr[q].cnt++;
		return q;
	}
	int mid=l+r>>1;
	if(x<=mid)tr[q].l=insert(tr[p].l,l,mid,x);
	else
		tr[q].r=insert(tr[p].r,mid+1,r,x);
	tr[q].cnt=tr[tr[q].l].cnt+tr[tr[q].r].cnt;
	return q;
}
int ask(int q,int p,int l,int r,int k)
{
	if(l==r)return l;
	int cnt=tr[tr[q].l].cnt-tr[tr[p].l].cnt;
	int mid=l+r>>1;
	if(k<=cnt)return ask(tr[q].l,tr[p].l,l,mid,k);
	else
		return ask(tr[q].r,tr[p].r,mid+1,r,k-cnt);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&a[i]);
    	b.pb(a[i]);
    }
    sort(b.begin(),b.end());
    b.erase(unique(b.begin(),b.end()),b.end());
    root[0]=build(0,b.size()-1);
    for(int i=1;i<=n;i++)
    	root[i]=insert(root[i-1],0,b.size()-1,find(a[i]));
    while(m--)
    {
    	int l,r,k;
    	scanf("%d%d%d",&l,&r,&k);
    	printf("%d\n",b[ask(root[r],root[l-1],0,b.size()-1,k)]);
    }
    return 0;
}
posted @ 2022-04-17 14:05  zyy2001  阅读(40)  评论(0编辑  收藏  举报