题目大意
无修改区间第K大数
解题思路
第一次实现了主席树,其实还有两种做法,一种是划分树,WJMZBMR说它是时代的眼泪了,于是我也没想……
一种是线段树套平衡树……拜托没必要,这道题又没有修改……
所谓主席树,就是可持久化线段树,也就是说我们每插入了一个新的元素,就创造了一个新的结点,这样下去,线段树所有的历史版本我们就都能保存下来。
然后考虑一下线段树相减,两棵线段树相减就是每一个结点相减(权值线段树不解释),那么我们每一个结点更新一次,那么序列中每一个元素都对应了一个版本的线段树,也就是序列中所有的前缀的权值线段树,那么对于一个区间,通过前缀相减很快就能搞出来这个区间对应的线段树,然后如何询问这棵线段树的第K大值……嘛……不用解释
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = (int)3e6 + 10;
const int maxn = (int)1e5;
int lson[N], rson[N], sum[N];
int Time[N], total;
int s[N], X[N];
void PushUp(int rt){
sum[rt] = sum[lson[rt]] + sum[rson[rt]];
}
int build(int L, int R){
int now = ++total;
if (L == R){
lson[now] = rson[now] = 0;
sum[now] = 0;
return now;
}
int m = (L + R) >> 1;
lson[now] = build(L, m);
rson[now] = build(m + 1, R);
PushUp(now);
return now;
}
int update(int rt, int pos, int L, int R){
int now = ++total;
if (L == R){
sum[now] = sum[rt] + 1;
lson[now] = rson[now] = 0;
return now;
}
int m = (L + R) >> 1;
if (pos <= m){
lson[now] = update(lson[rt], pos, L, m);
rson[now] = rson[rt];
}else{
rson[now] = update(rson[rt], pos, m+ 1, R);
lson[now] = lson[rt];
}
PushUp(now);
return now;
}
int ask(int rt1, int rt2, int k, int L, int R){
if (L == R) return X[L];
int number = sum[lson[rt2]] - sum[lson[rt1]];
int m = (L + R) >> 1;
if (number >= k)
return ask(lson[rt1], lson[rt2], k, L, m);
else
return ask(rson[rt1], rson[rt2], k - number, m +1, R);
}
int main(){
int cs; scanf("%d", &cs);
int n, m;
while(cs--){
scanf("%d%d", &n, &m);
int cnt = 0;
for (int i = 1; i <= n; i++){
scanf("%d", &s[i]);
X[++cnt] = s[i];
}
sort(X + 1, X + 1 + cnt);
int all = 1;
for (int i = 2; i <= cnt; i++)
if (X[i] != X[i - 1]) X[++all] = X[i];
total = 0;
Time[0] = build(1, all);
for (int i = 1; i <= n; i++){
int pos = lower_bound(X + 1, X + 1 + all, s[i]) - X;
Time[i] = update(Time[i - 1], pos, 1, all);
}
while(m--){
int L, R, k;
scanf("%d%d%d", &L, &R, &k);
printf("%d\n", ask(Time[L - 1], Time[R], k, 1, all));
}
}
return 0;
}