主席树
主席树
首先考虑一个比较经典的问题,你有一个静态的数列,每次询问一段区间 \(l \to r\) 内的第 \(k\) 小。
做法的一句话介绍,巨的人就不要往下翻了。
对于原序列的每个前缀维护一颗线段树,维护这个区间,并且这些线段树满足可减性。
接下具体解释下,
考虑一个静态区间上维护区间信息,一般会想到什么呢?
还记得我们在学线段树的时候曾经是需要维护一个可修改的区间。
我们一共三种做法。
第一种,直接用 \(a[]\) 记录,每次修改 \(O(1)\),查询 \(O(n)\)
第二种,通过差分的思想维护前缀和数组 \(sum[]\) 每次修改 \(O(n)\) ,查询 \(O(1)\)
第三种,则是线段树,查询,修改都是 \(O(log_n)\) 的。
那么这个问题只需要维护一段静态区间,那么就用差分的思想。
对于每个前缀都开一颗权值线段树,那么这样的话查询就变得十分的方便。
因为只要判断左边的大小是否大于 \(k\) 。最裸的线段树问题。
看似很简单,但是空间显然会炸。
你这么做的空间复杂度是 \(n * n << 2\) 的。
你不炸谁炸?
那么这个时候就可以用到动态开点了,不知道动态开点的可以去看我的 \(blog\)
这也是主席树的灵魂部分。
记录每个前缀的根,从根开始向前一颗线段树的子树连边。
具体的话是这样实现的。
\(a[]\) 是原数列,现在处理到 \(x\) 节点。
你就依次判断 \(mid\) 与 \(a[x]\) 之间的关系。
如果 \(mid\) 大于 \(a[x]\) 你就把第 \(x\) 颗线段树当前节点的右儿子连到第 \(x - 1\) 颗线段树对应的位置。
否则就把第 \(x\) 颗线段树当前节点的左儿子连到第 \(x - 1\) 颗线段树对应的位置。
至于另一边就不断递归就行了,最后只需要新建一个节点就行了。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#define N 200020
#define ls x << 1
#define rs x << 1 | 1
#define inf 0x3f3f3f3f
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
#define int long long
#define XRZ 1000000003
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
register int x = 0, f = 1; register char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int n, m, T[N << 5], Data[N << 5], Left[N << 5], Right[N << 5], a[N], b[N], rt;
int Build(int l, int r) {
int now = inc(rt);
if(l < r) Left[now] = Build(l, mid), Right[now] = Build(mid + 1, r);
return now;
}
int Updata(int pre, int l, int r, int x) {
int now = inc(rt);
Left[now] = Left[pre]; Right[now] = Right[pre]; Data[now] = Data[pre] + 1;
if(l >= r) return now;
if(x <= mid) Left[now] = Updata(Left[pre], l, mid, x);
else Right[now] = Updata(Right[pre], mid + 1, r, x);
return now;
}
int Query(int x, int y, int l, int r, int k) {
if(l == r) return l;
int std = Data[Left[y]] - Data[Left[x]];
if(std >= k) return Query(Left[x], Left[y], l, mid, k);
else return Query(Right[x], Right[y], mid + 1, r, k - std);
}
signed main() {
int n = read(), m = read();
Rep(i, 1, n) a[i] = b[i] = read();
sort(b + 1, b + n + 1);
int M = unique(b + 1, b + n + 1) - b - 1;
T[0] = Build(1, M);
Rep(i, 1, n) {
a[i] = lower_bound(b + 1, b + M + 1, a[i]) - b;
T[i] = Updata(T[i - 1], 1, M, a[i]);
}
Rep(i, 1, m) {
int x = read(), y = read(), z = read();
printf("%d\n", b[Query(T[x - 1], T[y], 1, M, z)]);
}
return 0;
}