[函数式线段树] POJ1442 Black Box

这两天看了看可持久化数据结构那篇paper,看看大牛的博客,自己想了想,感觉对函数式线段树有点领悟了。

虽然对我来说很难理解,也还是在慢慢理解。

但还是不太懂可持久化思想,也不太清楚函数式具体指什么,留给之后的我再考虑吧。

具体来看这题,我用的是函数式线段树,似乎也称为主席树。

做法是这样的,建立n棵线段树,每棵线段树维护的区间都是[1,sz],其中sz是序列a[1],a[2]...a[n]去重离散后个数。

任意a[i], 离散后必然在[1,sz]之间,所以对于第i棵线段树,就将离散后的a[1]~a[i]插入线段树。

比如对于a[j](1 <= j <= i),离散后是x, 那么第i棵线段树的从根到第x个叶子节点的维护值就相应+1。

非叶子节点的维护值就是区间和。

这种最朴素的做法方便理解,但时空复杂度都难以忍受,所以就有下面的优化啦。

假如已经建立第i-1棵线段树,那么第i棵就是在第i-1棵的基础上插入了离散后的a[i],对于非叶子节点,要么在左边插入,要么在右边插入,那么没有插入的子树和第i-1的子树的结构完全一样。所以其实有很多节点是不用新建的,就标记一下,指向第i-1棵线段树相同的儿子就行了。

一开始初始第0棵就好了。

然后是查询操作,为什么这么做就可以查询[L,R]第k大呢?

原因是对于区间[L,R],查询时只关注两棵线段树,root[L-1]和root[R]。这两棵树的不同是由于root[R]插入了离散后的a[L]~a[R],由于线段树内的值是经过离散化的,第k大就相当于第k个数,然后找到这个数就行啦,插入过程相当于计数排序。

以上是我的理解,欢迎指正和交流。

函数式线段树和划分树跑的时间一样...空间稍大,但编程复杂度严重降低,是种适合竞赛的优秀数据结构。我会说划分树我只会套模板么。

附代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 30005;
int root[N<<5], ls[N<<5], rs[N<<5], sum[N<<5];
int a[N], lisan[N];
int cnt = 0;
void build(int &rt, int l, int r){
    rt = ++cnt;
    if(l == r) return;
    int m = (l+r) >> 1;
    build(ls[rt], l, m);
    build(rs[rt], m+1, r);
}
void update(int u, int &v, int l, int r, int x){
    v = ++cnt;
    ls[v] = ls[u], rs[v] = rs[u], sum[v] = sum[u] + 1;
    if(l == r) return;
    int m = (l+r) >> 1;
    if(x <= m) update(ls[u], ls[v], l, m, x); //更新左边
    else update(rs[u], rs[v], m+1, r, x);  //更新右边
}
int query(int u, int v, int l, int r, int x){
    if(l == r) return l;
    int tmp = sum[ls[v]] - sum[ls[u]];
    int m = (l+r) >> 1;
    if(x <= tmp) return query(ls[u], ls[v], l, m, x);
    else return query(rs[u], rs[v], m+1, r, x-tmp);
}
int main(){
    int n, m, tmp;
    while(scanf("%d %d", &n, &m) != EOF){
        for(int i = 1; i <= n; ++i){
            scanf("%d", &a[i]);
            lisan[i] = a[i];
        }
        sort(lisan+1, lisan+n+1);
        int sz = unique(lisan+1, lisan+n+1) - lisan-1;
        build(root[0], 1, sz); //初始化第0棵树
        for(int i = 1; i <= n; ++i){
            int tmp = lower_bound(lisan+1, lisan+sz+1, a[i]) - lisan;
            update(root[i-1], root[i], 1, sz, tmp); //在前一棵树的基础上建立新树 减少空间
        }
        for(int i = 1; i <= m; ++i){
            scanf("%d", &tmp);
            int x = query(root[0], root[tmp], 1, sz, i);
            printf("%d\n", lisan[x]);
        }
    }
}



你可能感兴趣的:(线段树,poj,主席树)