假设你已经学会了可持久化……
例题
给定 n n n 个数, m m m 次静态询问区间第 k k k 大。 1 ≤ n , m ≤ 1 0 5 1\le n,m\le 10^5 1≤n,m≤105。
思想
先考虑 l = 1 l=1 l=1, r = n r=n r=n 的特殊情况。容易发现可以用一棵线段树来维护。
当 l = 1 l=1 l=1, r ∈ [ 1 , n ] r\in [1,n] r∈[1,n] 的时候,可以建立 n n n 棵线段树,但是修改很小,使用可持久化。
当 l l l 和 r r r 取任一值, l ≤ r l\le r l≤r 的时候,运用前缀和的思想, [ l , r ] = [ 1 , r ] − [ 1 , l − 1 ] [l,r] = [1,r] - [1,l-1] [l,r]=[1,r]−[1,l−1],用上一部的 n n n 棵线段树即可。
时间复杂度 O ( n × log n ) O(n\times \log n) O(n×logn)。
注意是求第 k k k 小。
但是值域线段树是开不下 1 0 9 10^9 109 的。
方法 1 1 1:离散化。
方法 2 2 2:动态插入。好写。但是有的时候不能用。
采用方法 2 2 2。
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;
const int N = 2e5 + 10;
struct Node {
int l, r, sum;
} z[N * 50];
int cnt;
int a[N], root[N];
void update(int p) {
z[p].sum = z[z[p].l].sum + z[z[p].r].sum;
}
// 当前在p对应的区间是l~r 把i位置+1
// 返回修改后的新的根节点
int modify(int p, int l, int r, int i) {
int pp;
z[pp = ++ cnt] = z[p];
if (l == r) {
z[pp].sum ++;
return pp;
}
int mid = l + r >> 1;
if (i <= mid)
z[pp].l = modify(z[p].l, l, mid, i);
else
z[pp].r = modify(z[p].r, mid + 1, r, i);
update(pp);
return pp;
}
int query(int rootl, int rootr, int l, int r, int k) {
if (l == r)
return l;
int mid = l + r >> 1;
int t = z[z[rootr].r].sum - z[z[rootl].r].sum;
if (t >= k)
return query(z[rootl].r, z[rootr].r, mid + 1, r, k);
else
return query(z[rootl].l, z[rootr].l, l, mid, k - t);
}
signed main() {
int n, k = -1e9, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> a[i];
for (int i = 1; i <= n; i ++)
a[i] += 1000000000;
for (int i = 1; i <= n; i ++)
k = max(k, a[i]);
for (int i = 1; i <= n; i ++)
root[i] = modify(root[i - 1], 1, k, a[i]);
// root[i] --> a1 ~ ai
while (m --) {
int l, r, p;
cin >> l >> r >> p;
cout << query(root[l - 1], root[r], 1, k, r - l - p + 2) - 1000000000 << '\n';
}
return 0;
}