zoukankan      html  css  js  c++  java
  • 可持久化线段树(主席树)新手向教程

    嗯今天来讲讲一个高端玩意,叫可持久化线段树。

    新手向,有点耐心是一定可以懂的

    知识储备

    首先你得知道线段树是什么,不然也不需要学这个东西
    线段树

    引入

    现在呢我们来思考一个问题,如果题目有需要保存线段树更改前的各个历史版本(比如给一个数列的前n项各建一棵线段树),我们应该怎么存?
    每个版本存一棵树吗?
    不不不太多了会爆空间的QAQ

    事实上,如果要存储前我们只需要修改少量点就可以,因为每两个版本间有很多的重复部分。

    解析


    圈里面是这个区间包含的数字在数列里出现的次数。
    比如:现在我们有数列 2 1 3 4
    黑色为只存储前1个数的线段树
    现在我们要储存前两个数,完全不必完全新一棵树,可以利用原来树上和现在要建的新树一样的部分,也就是如果原来树上的某段区间在新树中仍将保持不变,那我们就把这个点(包括它的整个子树)连到新的根节点下面,从而避免了新建这些部分所用的空间和时间。

    拿上面的图来说,因为数列1,2总共有两个元素而不是原来黑色树的一个,所以新建一个节点(最上面的红色那个)
    然后我们发现右子树(3~4)在新树中和原来的一样,所以不做修改,直接把右子树连到新的根节点上面
    再看左子树,发现发生了变化,所以创建新点,
    然后以此类推,就可以得到新的线段树(红色)
    p.s. 红色是前两个元素(2,1)的树

    嗯大家可以自己画个图模拟一下啊

    代码实现

    接下来考虑如何写代码
    首先我们需要建一棵各个值均为0的“空树”,这里注意一下,我们不得不使用指针,而不是像原来那样ls = pos << 1,rs = pos << 1 | 1了,因为不同的历史版本中同一个位置的点可能会有不同的编号。

    对于每次新建版本,从上次建出的树根开始递归,原来的和要新建的一样就不管了,不一样就新建节点。

    Code

    //by floatiy
    
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    const int MAXN = 200000 + 5;
    int n,m;
    int cnt,q;
    int a[MAXN],b[MAXN],head[MAXN];
    
    struct Tree{
        int sum;
        int ls,rs;
    }t[MAXN << 5];
    
    void update(int pos){
        t[pos].sum = t[t[pos].ls].sum + t[t[pos].rs].sum;
        return;
    }
    
    int build(int L,int R){
        int pos = ++cnt;
        int mid = (L + R) >> 1;
        t[pos].sum = 0;
        if(L < R){
            t[pos].ls = build(L,mid);
            t[pos].rs = build(mid + 1,R);
        }
        return pos;
    }
    
    int modify(int L,int R,int pre,int v){
    //  cout<<"DEBUG modify"<<endl;
        int pos = ++cnt;
        t[pos].ls = t[pre].ls;
        t[pos].rs = t[pre].rs;
        t[pos].sum = t[pre].sum + 1;
        if(L < R){
            int mid = (L + R) >> 1;
            if(v <= mid) t[pos].ls = modify(L,mid,t[pre].ls,v);
            else t[pos].rs = modify(mid + 1,R,t[pre].rs,v);
        }
    //  update(pos);
        return pos;
    }
    
    int query(int L,int R,int ll,int rr,int k){
        if(L >= R) return L;
        int mid = (L + R) >> 1;
        int x = t[t[rr].ls].sum - t[t[ll].ls].sum;
        if(x >= k) return query(L,mid,t[ll].ls,t[rr].ls,k);
        else return query(mid + 1,R,t[ll].rs,t[rr].rs,k - x);
    }
    
    int main(){
        cin>>n>>q;
        for(int i = 1;i <= n;i++){
            scanf("%d",&a[i]);
            b[i] = a[i];
        }
        sort(b + 1,b + 1 + n);
        m = unique(b + 1,b + 1 + n) - b - 1;
        head[0] = build(1,m);
        for(int i = 1;i <= n;i++){
            int x = lower_bound(b + 1,b + 1 + m,a[i]) - b;
            head[i] = modify(1,m,head[i - 1],x);
        }
    //  for(int i = 1;i <= cnt;i++){
    //      cout<<"DEBUG: "<<i<<":   "<<head[i]<<" "<<t[i].ls<<" "<<t[i].rs<<" "<<t[i].sum<<endl;
    //  }
        while(q--){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            int ans = query(1,m,head[x - 1],head[y],z);
            printf("%d
    ",b[ans]);
        }
        return 0;
    }

    某某杂

    据说 是主席在不会写分块的情况下发明的替代品

  • 相关阅读:
    水调歌头·1024
    网页开发方式-从静态页面到服务端渲染
    完美融合 nextjs 和 antd
    commanderJs编写命令行工具(cli)
    [信息安全] 05 X.509 公钥证书的格式标准
    [Cake] 3. dotnet 本地工具 cake & dotnet format
    [计算机网络] 00 概述
    [Cake] 2. dotnet 全局工具 cake
    [解读REST] 0.REST 相关参考资料
    [OIDC in Action] 3. 基于OIDC(OpenID Connect)的SSO(添加Github OAuth 2.0的支持)
  • 原文地址:https://www.cnblogs.com/floatiy/p/9472340.html
Copyright © 2011-2022 走看看