静态区间第k小
题目描述
如题,给定NNN个正整数构成的序列,将对于指定的闭区间查询其区间内的第KKK小值。
输入格式
第一行包含两个正整数NNN、MMM,分别表示序列的长度和查询的个数。
第二行包含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 101≤N,M≤10
对于50%的数据满足:1≤N,M≤1031 \leq N, M \leq 10^31≤N,M≤103
对于80%的数据满足:1≤N,M≤1051 \leq N, M \leq 10^51≤N,M≤105
对于100%的数据满足:1≤N,M≤2⋅1051 \leq N, M \leq 2\cdot 10^51≤N,M≤2⋅105
对于数列中的所有数aia_iai,均满足−109≤ai≤109-{10}^9 \leq a_i \leq {10}^9−109≤ai≤109
样例数据说明:
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的数的个数>=k>=k>=k,则答案一定<=x<=x<=x,否则答案一定>x>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;
}