您的位置:首页 > 其它

poj 2104 区间第k大 主席树

2015-08-05 20:25 435 查看
/*
静态主席树求区间第K大
poj 2104

主要思想:
1. 将数据离散化
2. 用线段树来维护信息,维护处于该区间的数的个数
3. 将原有数组中的每一个数依次插入,每次插入一个值的时候新建一棵线段树
这样就有了n+1棵线段树,对于询问(l,r,k),只需查询T[l-1]和T[r]两棵线段树即可,其中T[i]为插入
第i个数后的线段树询问过程,只要利用二分思想:若这两棵线段树的左儿子区间的数的个数差不小于k,
则答案往左儿树中找,否则往右子树找。
4. 上述建树空间复杂度肯定太大,所以要空间重用。
注意当插入一个数的时候,插入后的线段树(新)和插入前的线段树(旧)之间有很多信息都是一样的,
容易发现只有从修改位置到根的log(n)的路径是不一样的,所以新树中的其他不变的子树只要重用旧子树
的相应位置就好,这样每次建树空间复杂度为log(n)了。
5. 详细见代码
*/
#include <cstdio>
#include <algorithm>

#define m (l+r)/2
using namespace std;

const int N = 100100;
const int M = N * 30;

int n, q, num, a
, b
, l, r, k;
int T[M], cnt[M], lson[M], rson[M], tot;
int hash(int a){ return lower_bound(b + 1, b + num + 1, a) - b; }

int init(int l, int r){
int root = tot ++;
cnt[root] = 0;
if(l < r) lson[root] = init(l, m), rson[root] = init(m + 1, r);
return root;
}
int upd(int pre_root, int pos, int val){
int cur_root = tot ++, ret = cur_root;
cnt[cur_root] = cnt[pre_root] + val;
int l = 1, r = num;
while(r > l){
if(pos <= m){
rson[cur_root] = rson[pre_root];
cur_root = lson[cur_root] = tot ++;
pre_root = lson[pre_root];
cnt[cur_root] = cnt[pre_root] + val;
r = m;
}
else {
lson[cur_root] = lson[pre_root];
cur_root = rson[cur_root] = tot ++;
pre_root = rson[pre_root];
cnt[cur_root] = cnt[pre_root] + val;
l = m + 1;
}
}
return ret;
}
int query(int pre, int cur, int k){
int l = 1, r = num;
while(r > l){
if(k <= cnt[lson[cur]] - cnt[lson[pre]]){
pre = lson[pre];
cur = lson[cur];
r = m;
}
else {
k -= cnt[lson[cur]] - cnt[lson[pre]];
pre = rson[pre];
cur = rson[cur];
l = m + 1;
}
}
return l&r;
}

int main(){
while(~scanf("%d %d", &n, &q)){
for(int i = 1; i <= n; i ++) scanf("%d",&a[i]), b[i] = a[i];
sort(b + 1, b + n + 1);
num = unique(b + 1, b + n + 1) - b - 1;
tot = 0;
T[0] = init(1, num);
for(int i = 1; i <= n; i ++) T[i] = upd(T[i-1], hash(a[i]), 1);
while(q --){
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", b[query(T[l - 1], T[r], k)]);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: