代码风格参考了这篇博客:http://www.cnblogs.com/elpsycongroo/p/7296139.html
理解主席树主要是这篇博客:http://www.cnblogs.com/elpsycongroo/p/7296139.html
摘抄了一段主席树的解释:所谓主席树呢,就是对原来的数列[1..n]的每一个前缀[1..i](1≤i≤n)建立一棵线段树,线段树的每一个节点存某个前缀[1..i]中属于区间[L..R]的数一共有多少个(比如根节点是[1..n],一共i个数,sum[root] = i;根节点的左儿子是[1..(L+R)/2],若不大于(L+R)/2的数有x个,那么sum[root.left] = x)。若要查找[i..j]中第k大数时,设某结点x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在结点x内的数字总数。而对每一个前缀都建一棵树,会MLE,观察到每个[1..i]和[1..i-1]只有一条路是不一样的,那么其他的结点只要用回前一棵树的结点即可,时空复杂度为O(nlogn)。
自己的代码丑陋,见谅
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
struct node {
int l, r, cnt;
/*
注意!!!
l,r不是指区间,而是节点的序号
cnt是有多少个数字出现
*/
} T[maxn * 20];
int len, n, m, a[maxn], t[maxn], root[maxn], tot;
// root 保存的是所有历史记录
int build(int l, int r) {
int rt = ++tot;
T[rt].l = 0;
T[rt].r = 0;
T[rt].cnt = 0;//建立一棵空树
if(l == r) return rt;
int mid = (l + r) >> 1;
T[rt].l = build(l, mid);//左右递归建立
T[rt].r = build(mid + 1, r);
return rt;
}
int updata(int l, int r, int pre, int x) {
int rt = ++tot;
T[rt] = T[pre];//相当于连接新节点和以前节点的左右子树
T[rt].cnt++;
if(l == r) return rt;
int mid = (l + r) >> 1;//每次二分添加一个新节点
if(mid >= x)T[rt].l = updata(l, mid, T[pre].l, x);
else T[rt].r = updata(mid + 1, r, T[pre].r, x);
return rt;
}
int query(int l, int r, int pre, int rt, int k) {
if(l == r) return l;
int mid = (l + r) >> 1;
int sum = T[T[rt].l].cnt - T[T[pre].l].cnt;//每次比较两个节点的左子树
if(sum >= k) return query(l, mid, T[pre].l, T[rt].l, k);
else return query(mid + 1, r, T[pre].r, T[rt].r, k - sum);//如果在右子树上别忘了减去sum
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
t[i] = a[i];
}
sort(t + 1, t + 1 + n);//必须离散化
len = unique(t + 1, t + 1 + n) - t - 1;
for(int i = 1; i <= n; i++) {
a[i] = lower_bound(t + 1, t + 1 + len, a[i]) - t;
}
root[0] = build(1, n);
for(int i = 1; i <= n; i++) {
root[i] = updata(1, n, root[i - 1], a[i]);
}
for(int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
printf("%d\n", t[query(1, n, root[u - 1], root[v], w)]);
}
return 0;
}