zoukankan      html  css  js  c++  java
  • 主席树备忘

    主席树备忘

    非堆式动态建树

    传统线段树都是通过(cur<<1)(cur<<1|1)分别访问左右儿子,而非堆式建树使用(L[])(R[])访问左右儿子,儿子节点编号动态使用(++topt)分配(如分配(cur)新的一个左二子节点:L[cur] = ++topt;)

    主席树为了节省空间,就是通过这种建树方式建树的

    void build(int &cur, ...){
      if(cur==0)
      	cur = ++topt;
    	//...
    }
    

    主席树

    主席树为每一个历史节点都新建了一棵线段树以供随时查询历史版本,但是除了第一棵是完全二叉树,其余都是不完全的线段树(如果每个历史节点全部新建就会MLE),主席树会共享没有改变过的节点。

    记得存下每个历史节点的线段树的根节点以供访问。

    洛谷 P3834 模板

    题面,题意摘要:给定序列,每次询问区间([l,r])的的第(k)小值

    考虑依次对插入的数,每个数都建一棵值域线段树,树节点维护一个区间([a,b])即在第(a)小到第(b)小范围内目前数的个数。依输入顺序插入数,并查询其排名(所以最好离散化一下),在这个当前新树下,将所有包含这个数的区间都+1

    查询区间([l,r])时,用前缀和思想,将第(r)个数的线段树减去第(l-1)个数,得出一棵包含区间([l,r])内各数分布情况的树,再像Splay那样,每次查询小于当前范围数的个数,如果大于(k),则第(k)个数一定在前面那个范围里,反之,则在后面那个区间里。

    关于空间复杂度,除了第一棵最大(4n)个节点,其余线段树每次只会最多新建(log^n_2)个节点,总空间复杂度为(4n+(n-1)log^n_2)(相对于原来的(4n^2))

    AC Code

    #include <cstdio>
    #include <algorithm>
    #define MAXN 200010
    #define MAXM MAXN*20
    using namespace std;
    int n,q,sz,L[MAXM],R[MAXM],val[MAXM];
    int a[MAXN],s[MAXM],his[MAXN];
    int topt;
    int query(int a, int b, int l, int r, const int k){
        if(l==r) return l;
        int sz = val[L[b]]-val[L[a]];
        int mid = (l+r)>>1;
        if(sz>=k) return query(L[a], L[b], l, mid, k);
        else return query(R[a], R[b], mid+1, r, k-sz);
    }
    void add(int &cur, const int pre, int l, int r, int x){
        cur=++topt;
        L[cur]=L[pre], R[cur]=R[pre],val[cur]=val[pre]+1;
        if(l==r) return;
        int mid=(l+r)>>1;
        if(x<=mid) add(L[cur], L[pre], l, mid, x);
        else add(R[cur], R[pre], mid+1, r, x);
    }
    void buildt(int &cur, int l, int r){
        cur=++topt;
        if(l<r){
            int mid = (l+r)>>1;
            buildt(L[cur], l, mid);
            buildt(R[cur], mid+1, r);
        }
    }
    int main(){
        scanf("%d %d", &n, &q);
        for(register int i=1;i<=n;++i)
            scanf("%d", &a[i]), s[i]=a[i];
        sort(s+1, s+1+n);
        sz = unique(s+1, s+1+n) - (s+1);
        buildt(his[0], 1, sz);
        for(register int i=1;i<=n;++i){
            int t = lower_bound(s+1, s+1+sz, a[i]) - s;
            add(his[i], his[i-1], 1, sz, t);
        }
        while(q--){
            int l,r,k;
            scanf("%d %d %d", &l, &r, &k);
            printf("%d
    ", s[query(his[l-1], his[r], 1, sz, k)]);
        }
        return 0;
    }
    

    洛谷 P3919 模板

    题面,真·模板题

    切掉上面那道假模板后,此题顿觉轻松

    #include <cstdio>
    #define MAXN 1000001
    #define MAXM (20*MAXN)
    #define mid ((l+r)>>1)
    #define SAFE(A) ((A)<0?0:(A))
    using namespace std;
    int n,m,tot,a[MAXN],his[MAXN];
    int L[MAXM],R[MAXM],V[MAXM];
    void buildt(int &cur, int l, int r){
        cur=++tot;
        if(l==r){
            V[cur]=a[l];
            return;
        }
        buildt(L[cur], l, mid);
        buildt(R[cur], mid+1, r);
    }
    void change(int &cur, int pre, int x, int val, int l, int r){
        cur=++tot;
        L[cur]=L[pre],R[cur]=R[pre],V[cur]=V[pre];
        if(l==r){
            V[cur]=val;
            return;
        }
        if(x<=mid) change(L[cur], L[pre], x, val, l, mid);
        else change(R[cur], R[pre], x, val, mid+1, r);
    }
    int query(int cur, int x, int l, int r){
        if(l==r){
            return V[cur];
        }
        if(x<=mid) return query(L[cur], x, l, mid);
        return query(R[cur], x, mid+1, r);
    }
    int main()
    {
        scanf("%d %d", &n, &m);
        for(register int i=1;i<=n;++i) scanf("%d", &a[i]);
        buildt(his[0], 1, n);
        for(register int i=1;i<=m;++i){
            int ver,opt;scanf("%d %d", &ver, &opt);
            if(opt==1){
                int x,val;scanf("%d %d", &x, &val);
                change(his[i], his[ver], x, val, 1, n);
            }else{
                int x;scanf("%d", &x);
                his[i]=his[ver];
                printf("%d
    ", query(his[ver], x, 1, n));
            }
        }
        return 0;
    }
    
  • 相关阅读:
    进程与线程的一个简单解释
    如何更优雅的写出你的SQL语句
    SQL 性能优化梳理
    如何写出让同事无法维护的代码?
    Linux配置IP常用命令
    Linux中防火墙命令笔记
    蓝牙技术的工作原理及用途
    别死写代码,这 25 条比涨工资都重要
    搞清这些陷阱,NULL和三值逻辑再也不会作妖
    计算机网络:TCP和UDP的对比
  • 原文地址:https://www.cnblogs.com/santiego/p/10799957.html
Copyright © 2011-2022 走看看