Luogu P3834 - 【模板】可持久化线段树 2
题意
给定一个序列,不对其进行任何修改操作。进行 m m m次询问,每次给定两个数 l , r ( 1 ≤ l ≤ r ≤ n ≤ 2 × 1 0 5 ) l,r(1\leq l \leq r \leq n \leq 2\times10^5) l,r(1≤l≤r≤n≤2×105),问在区间 [ l , r ] [l,r] [l,r]内的第 k k k小的数是多少。
做法
现在假设题目只有一次查询,假设询问 [ 1 , r ] [1,r] [1,r]内的第 k k k小,此时我们可以采用权值线段树存储 [ 1 , r ] [1,r] [1,r]序列。对于当前节点,如果想要查询的 k k k小于左子树的大小,那么第 k k k个数一定落在左子树上,否则一定落在右子树上。当落在左子树上时,直接递归查询左子树里第 k k k小;当落在右子树上时,设左子树大小为 s s s,直接递归查询右子树里第 k − s k-s k−s小。
现在扩展问题,如果有 m m m次询问,第 i i i次询问区间 [ 1 , r i ] [1,r_i] [1,ri]内的第 k k k小。我们如果直接按照刚刚的方式每遇到一个 r i r_i ri就建立一棵权值线段树,空间是吃不消的。这个时候,就要用上主席树的思想了。我们发现,如果我们从 1 1 1到 n n n都建立一个 [ 1 , r ] [1,r] [1,r]的权值线段树,那么相邻两个权值线段树中的权值,只有一个权值不同,且权值变化值只有 1 1 1。也就是说,两棵相邻的权值线段树中间会有非常多的相同的节点,存储相同节点意味着空间的浪费。所以采用主席树的思想,我们从 [ 1 , r ] [1,r] [1,r]的权值线段树扩展到 [ 1 , r + 1 ] [1,r+1] [1,r+1]时,我们只关注变化的那个数值,而且只有从根节点到那一个权值变化的点的那条路径有变化。所以建立 [ 1 , r + 1 ] [1,r+1] [1,r+1]时只需要多开 log n \log n logn个节点。这样针对每一个 [ 1 , r i ] [1,r_i] [1,ri],只要我们找到对应的 r i r_i ri的根节点就可以完成查询。
现在再扩展问题,把询问变成 [ l , r ] [l,r] [l,r]。因为我们权值线段树的建立是按照相同数字的数量,所以可以采取前缀和的思想,通过两个权值线段树 [ 1 , l − 1 ] [1,l-1] [1,l−1], [ 1 , r ] [1,r] [1,r]求差值求出区间的权值线段树,通过最开始的思路就可以求出静态区间 [ l , r ] [l,r] [l,r]的第 k k k小了。
数据范围很大,建立权值线段树的时候需要先进行离散化。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, m, mi, tot;
int pre[200050], hsh[200050];
struct num {
int id, x, mp;
}a[200050];
struct Tree {
int l, r, cnt, ls, rs;
}t[6000050];
void pu(int ni) {
t[ni].cnt = t[t[ni].ls].cnt + t[t[ni].rs].cnt;
}
int build_tree(int l, int r) {
int ni = ++tot;
t[ni].l = l; t[ni].r = r; t[ni].cnt = 0;
if (l == r) {
t[ni].ls = t[ni].rs = -1;
return ni;
}
int mid = (l + r) >> 1;
t[ni].ls = build_tree(l, mid);
t[ni].rs = build_tree(mid + 1, r);
pu(ni);
return ni;
}
int fix(int li, int l, int r, int pos, int x) {
int ni = ++tot;
t[ni].l = l; t[ni].r = r;
if (l == r) {
t[ni].ls = t[ni].rs = -1;
t[ni].cnt = t[li].cnt + x;
return ni;
}
int mid = (l + r) >> 1;
if (pos <= mid) {
t[ni].rs = t[li].rs;
t[ni].ls = fix(t[li].ls, l, mid, pos, x);
}
else {
t[ni].ls = t[li].ls;
t[ni].rs = fix(t[li].rs, mid + 1, r, pos, x);
}
pu(ni);
return ni;
}
int query(int li, int ri, int l, int r, int k) {
if (l >= r) {
return hsh[l];
}
int mid = (l + r) >> 1;
int delta = t[t[ri].ls].cnt - t[t[li].ls].cnt;
if (k <= delta) {
return query(t[li].ls, t[ri].ls, l, mid, k);
}
else return query(t[li].rs, t[ri].rs, mid + 1, r, k - delta);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
mi = tot = 0;
for (int i = 1; i <= n; ++i) {
cin >> a[i].x;
a[i].id = i;
}
sort(a + 1, a + n + 1, [](const num &A, const num &B) {
return A.x < B.x;
});
a[1].mp = 1;
hsh[a[1].mp] = a[1].x;
for (int i = 2; i <= n; ++i) {
if (a[i].x == a[i - 1].x) a[i].mp = a[i - 1].mp;
else a[i].mp = a[i - 1].mp + 1;
hsh[a[i].mp] = a[i].x;
}
mi = a[n].mp;
sort(a + 1, a + n + 1, [](const num &A, const num &B) {
return A.id < B.id;
});
pre[0] = build_tree(1, mi);
for (int i = 1; i <= n; ++i) {
pre[i] = fix(pre[i - 1], 1, mi, a[i].mp, 1);
}
for (int i = 1; i <= m; ++i) {
int x, y, k;
cin >> x >> y >> k;
cout << query(pre[x - 1], pre[y], 1, mi, k) << "\n";
}
return 0;
}