题目背景
这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值。
输出格式:
输出包含k行,每行1个正整数,依次表示每一次查询的结果
输出样例#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
说明
数据范围:
对于20%的数据满足: 1 \leq N, M \leq 101≤N,M≤10
对于50%的数据满足: 1 \leq N, M \leq 10^31≤N,M≤103
对于80%的数据满足: 1 \leq N, M \leq 10^51≤N,M≤105
对于100%的数据满足: 1 \leq N, M \leq 2\cdot 10^51≤N,M≤2⋅105
对于数列中的所有数 a_iai ,均满足 -{10}^9 \leq a_i \leq {10}^9−109≤ai≤109
样例数据说明:
N=5,数列长度为5,数列从第一项开始依次为 [25957, 6405, 15770, 26287, 26465 ][25957,6405,15770,26287,26465]
第一次查询为 [2, 2][2,2] 区间内的第一小值,即为6405
第二次查询为 [3, 4][3,4] 区间内的第一小值,即为15770
第三次查询为 [4, 5][4,5] 区间内的第一小值,即为26287
第四次查询为 [1, 2][1,2] 区间内的第二小值,即为25957
第五次查询为 [4, 4][4,4] 区间内的第一小值,即为26287
思路
既然是主席树的模板题,就大概说一说主席树。
题目的要求是求给定的区间第k大元素,首先如果这道题不是给定区间[L,R]
,而是求一棵权值线段树上的第k
大,这个的求法是什么,这样就比较容易了,就是这个题HDU2852 KiKi’s K-Number(值域线段树,求线段树第k大),在线段树上叶子节点表示这个节点的值出现了几次,然后就像普通的线段树一样更新上去,要查找第k
大的时候,就从根节点出发先看左子树的节点数x
是否大于等于k
,如果大于等于k
就向左子树找,如果小于k
,就去右子树找,去右子树找的时候k
就变成了x-k
,最后就可以找到第k大的位置。
主席树是什么,主席树也被称做可持久化线段树,它采取动态建树的方式建了一个有n个节点的线段树,由于是动态开点,动态建树,所以复杂度是
,建立方法是首先建立一棵空树,然后依次把给出序列的值插入这棵空树,每插入一个值,就新开一个根节点建立线段树,第i个线段树存储的信息是区间[1,i]
的信息,所以面对询问求[l,r]
的第k大,我们就可以转化成sum[r]-sum[l-1]
类似前缀和相减的方式来把[1,r]
和[1,l-1]
这两棵线段树进行相减,然后求第k大的时候也是边递归边求第k大。
详细的讲解请参考这个博客:主席树详解
代码
#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <sstream>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#include <list>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
const int N = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int node_cnt, n, m;
int sum[N << 5], rt[N], lc[N << 5], rc[N << 5];
int a[N], b[N], p; //原序列和离散序列和修改点
void build(int &t, int l, int r)
{
t = ++node_cnt;
if (l == r)
return;
int mid = (l + r) >> 1;
build(lc[t], l, mid);
build(rc[t], mid + 1, r);
}
int modify(int o, int l, int r)
{
int oo = ++node_cnt;
lc[oo] = lc[o];
rc[oo] = rc[o];
sum[oo] = sum[o] + 1;
if (l == r)
return oo;
int mid = (l + r) >> 1;
if (p <= mid)
lc[oo] = modify(lc[oo], l, mid);
else
rc[oo] = modify(rc[oo], mid + 1, r);
return oo;
}
int query(int u, int v, int l, int r, int k) //求u,v这两棵线段树的差的树中的第k大
{
int mid = (l + r) >> 1, ans;
int x = sum[lc[v]] - sum[lc[u]];
if (l == r)
return l;
if (x >= k)
ans = query(lc[u], lc[v], l, mid, k);
else
ans = query(rc[u], rc[v], mid + 1, r, k - x);
return ans;
}
void init()
{
node_cnt = 0;
}
int main()
{
//freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
init();
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int q = unique(b + 1, b + n + 1) - (b + 1);
build(rt[0], 1, q);
for (int i = 1; i <= n; i++)
{
p = lower_bound(b + 1, b + q + 1, a[i]) - b;
rt[i] = modify(rt[i - 1], 1, q);
}
int l, r, k;
while (m--)
{
scanf("%d%d%d", &l, &r, &k);
int ans = query(rt[l - 1], rt[r], 1, q, k);
printf("%d\n", b[ans]);
}
return 0;
}