zoukankan      html  css  js  c++  java
  • 可持久化线段树(主席树)

    如果有m次修改,就会存在m+1个版本
    如果某一个结点的信息发生的变化,就会创造一个全新的结点出来

    struct{
      int l,r;//表示左右子节点的下标,而不是表示其区间的左右端点
      int cnt;//当前区间中一共有多少个数 
    }; 

    可持久化线段树难以进行区间修改的操作。因为它难以处理懒标记,因为有很多版本。
    主席树中每个版本的结点个数一样,所以比较容易一点。
    若是修改了一条路径,就创建条修改后的路径。

     

    修改了左边红色路径上的信息,那么就重新创建右边这样路径的修改后的信息。红色的横线连接的两个相同意义的点。绿色的线连接的是修改后的点和原先并未修改的子结点

    按照数值而不是下标上建立线段树,维护每个数值区间中一共有多少个数
    如何求整体的第k小数:mid=1+n>>1,如果[1,mid]之间的数的个数cnt>=k,那么就递归左边,否则递归右边
    如何求[1,R](原序列的下标)中的第k小数:在可持久化线段树(从左往右,给出一个点就更新一个版本)中只需要找到第R个版本,然后搜这个版本的第k小数就可以了
    如和求[L,R](原序列的下标)中的第k小数:
    在第R个版本中[l,r](数值)的个数是cnt1,在l-1版本中[l,r](数值)的个数是cnt2.cnt1-cnt2就是[L,R](原序列区间)中[l,r](数值)中的数的个数

    一个典型的例题:https://www.acwing.com/problem/content/description/257/

    题目描述

    给定长度为N的整数序列A,下标为 1N

    现在要执行M次操作,其中第i次操作为给出三个整数li,ri,ki,求A[li],A[li+1],…,A[ri][(即A的下标区间[li,ri中]第ki小的数是多少。

    输入格式

    第一行包含两个整数N和M。

    第二行包含N个整数,表示整数序列A。

    接下来M行,每行包含三个整数li,ri,ki用以描述第i次操作。

    输出格式

    对于每次操作输出一个结果,表示在该次操作中,第k小的数的数值。

    每个结果占一行。

    数据范围

    N1e5,M1e4,|A[i]|1e9

    AC代码(主席树模板代码):

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10,M=1e4+10;
    struct node{
        int l,r;//表示左右子节点的空间下标,而不是表示其区间的左右端点
        int cnt;//表示该数值区间中有多少个数 
    }tr[4*N+17*N];//线段树第0版本的空间+N(修改次数、版本空间)*logn
    
    int root[N],id=0,a[N];//root每个版本的头指针;id每增加一个结点的空间下标 
    vector<int>all;
    
    void pushup(int u){//更新结点信息 
        tr[u].cnt=tr[tr[u].l].cnt+tr[tr[u].r].cnt;
    }
    
    int build(int l,int r){//创建一颗树。也就是最初版本的线段树。l,r是离散化后的数值的下标 
        int p=id++;//id是还未使用的数组的第一个下标,创建的结点的空间下标就是此下标 
        if(l==r) {//当l==r时,表示到达了叶子节点 
            tr[p].cnt=0;
            return p;
        }
        int mid=l+r>>1;
        tr[p].l=build(l,mid);//左右递归创建线段树 
        tr[p].r=build(mid+1,r);
        return p;
    } 
    
    int find(int x){//二分搜索,查找离散化后的下标 
        return lower_bound(all.begin(),all.end(),x)-all.begin();
    }
    
    int insert(int p,int l,int r,int x){//插入一个数值,也就是更新一个版本。p是上一个版本对应的下标 
        int q=id++;
        tr[q]=tr[p];//首先将上一个版本的对应结点的信息复制过来 
        if(l==r){//说明到达了需要插入的结点的位置 
            tr[q].cnt++;
            return q;
        }
        int mid=l+r>>1;//二分插入 
        if(x<=mid) tr[q].l=insert(tr[p].l,l,mid,x);
        else tr[q].r=insert(tr[p].r,mid+1,r,x);
        pushup(q);//向上更新 
        return q;
    }
    
    int query(int q,int p,int l,int r,int k){
        if(l==r) return l;//表明到达了要查询的第k小结点 
        int cnt=tr[tr[q].l].cnt-tr[tr[p].l].cnt;//判断在[L,R]区间内有多少给定的区间内的数 
        int mid=l+r>>1;//二分 
        if(k>cnt) return query(tr[q].r,tr[p].r,mid+1,r,k-cnt);//递归查询 
        else return query(tr[q].l,tr[p].l,l,mid,k); 
    }
    
    int main(){
        int n,m;scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
            all.push_back(a[i]);
        }
        //将数值进行离散化 
        sort(all.begin(),all.end());
        all.erase(unique(all.begin(),all.end()),all.end());
        root[0]=build(0,all.size()-1);//0,all.size()-1是数值端点离散化之后的左右端点
        for(int i=0;i<n;i++)
            root[i+1]=insert(root[i],0,all.size()-1,find(a[i]));//创建多个版本 
        int l,r,k;
        while(m--){
            scanf("%d%d%d",&l,&r,&k);
            cout<<all[query(root[r],root[l-1],0,all.size()-1,k)]<<endl;
        }
        return 0;
    }
     

    写于:2020/9/1 19:23


    作者:孙建钊
    出处:http://www.cnblogs.com/sunjianzhao/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    离线修改注册表
    在Spring3中,配置DataSource的方法有6种。
    windows设置java环境变量
    三种配置linux环境变量的方法(以java为例)
    java call sap
    Tomcat 解决The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit
    window上安装pymysql
    Python中的str与bytes之间的转换的三种方法
    安装sqlite3.8的方法
    os.walk的用法
  • 原文地址:https://www.cnblogs.com/sunjianzhao/p/13598058.html
Copyright © 2011-2022 走看看