• 区间第K小——可持久化线段树模板


    概念

    可持久化线段树又叫主席树,之所以叫主席树是因为这东西是fotile主席创建出来的。
    可持久化数据结构思想,就是保留整个操作的历史,即,对一个线段树进行操作之后,保留访问操作前的线段树的能力。
    最简单的方法,每操作一次,建立一颗新树。这样对空间的需求会很大。而注意到,对于点修改,每次操作最多影响 $O(log n)$ 个节点,于是,其实操作前后的两个线段树,结构一样,因此可以共享未被影响的节点,被影响的就新建节点。于是,这样的线段树,每次操作需要O(log2(n))的空间。
    线段树对于每个n的分解是唯一的,所以n相同的线段树结构相同,这也是实现可持久化线段树的基础。

    分析

    对于区间第K小,
    先用 sort + unique 进行离散化操作;然后以离散化后的元素作为底层(接下来大概就是用线段树按顺序记录每个数字出现的次数)。
    关键来了:以不同的区间建立不同的版本,及1..1为1号,1..2为2号,1..7为7号,以此类推。 查询区间 l..r 需要查询两个版本:ver[l-1]ver[r],同步查询两棵树,并对查询到的内容相减便是l..r中该范围内的数据(以数字出现字数为关键词)。相当于线段树的前缀和
    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 2e5 + 100;
    int n, m, size_disc;  //size_disc是离散化之后的长度
    int n_init[maxn], n_disc[maxn];  //原数组   离散化后的数组
    int rt[maxn], lc[maxn << 5], rc[maxn << 5], sum[maxn << 5];  //rt:不同版本的根节点   lc/rc: 左儿子、右儿子(公用)  sum: 和(公用)
    int node_cnt, pnt_disc;    //node总计数, pnt_disc: A中数字对应B中的值
    
    void build(int& last_node, int l, int r)
    {
        last_node = ++ node_cnt;
        sum[last_node] = 0;
        if(l == r)  return;
        int mid = (l + r) >> 1;
        build(lc[last_node], l, mid);
        build(rc[last_node], mid+1, r);
    }
    
    
    int modify(int pre_rt, int l, int r)
    {
        int new_rt = ++node_cnt;
        lc[new_rt] = lc[pre_rt];
        rc[new_rt] = rc[pre_rt];
        sum[new_rt] = sum[pre_rt] + 1;
    
        int mid = (l + r) >> 1;
        if(l == r)  return new_rt;
        if(mid >= pnt_disc)  lc[new_rt] = modify(lc[new_rt], l, mid);
        else  rc[new_rt] = modify(rc[new_rt], mid+1, r);
        return new_rt;
    }
    
    
    int query(int rt1, int rt2, int k, int l, int r)
    {
        //printf("rt1:%d  rt2:%d  k:%d  l:%d  r:%d  ", rt1, rt2, k, l, r);
        if(l == r)  return l;
        int mid = (l + r) >> 1;
        int tmp = sum[lc[rt2]] - sum[lc[rt1]];
        //int tmp=sum[lc[rt2]]-sum[lc[rt1]];
    
        //printf("tmp:%d  k:%d
    ", tmp, k);
    
        int ans;
        if(tmp >= k)  ans = query(lc[rt1], lc[rt2], k, l, mid);
        else ans = query(rc[rt1], rc[rt2], k-tmp, mid+1, r);
        return ans;
    }
    
    void print_debug()
    {
        printf("node_cnt: %d
    ", node_cnt);
        for(int i = 0;i <= node_cnt;i++)
            printf("%d  lc:%d  rc:%d  sum:%d
    ", i, lc[i], rc[i], sum[i]);
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1;i <= n;i++)
        {
            scanf("%d", &n_init[i]);
            n_disc[i] = n_init[i];
        }
        sort(n_disc+1, n_disc+n+1);     //先排序再进行离散化
        size_disc = unique(n_disc+1, n_disc+n+1) - (n_disc+1);
    
        node_cnt = 0;
        build(rt[0], 1, size_disc);
        for(int i = 1;i <= n;i++)
        {
            pnt_disc = lower_bound(n_disc+1, n_disc+size_disc+1, n_init[i]) - n_disc;  //改了1
            rt[i] = modify(rt[i-1], 1, size_disc);  //只在上一个版本的基础上修改
        }
    
        for(int i = 0;i <m;i++)
        {
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);l--;
            int ans = query(rt[l], rt[r], k, 1, size_disc);
            printf("%d
    ", n_disc[ans]);
        }
    }

    其中,遍历1~n建立n棵线段树的过程如下:

    其中第i棵是建立在i-1棵的基础上,加上它们的n相同,结构也完全相同,两颗线段树相减就是这两个操作之间的变化值。

    参考链接:

    1. https://yfli.site/note14/

    2. https://www.luogu.org/problemnew/solution/P3834

  • 相关阅读:
    Android 布局中 如何使控件居中
    VGA, QVGA, HVGA, WVGA, FWVGA和iPhone显示分辨率
    [转+整理] Android 分辨率,密度,像素单位说明
    多线程的知识点总结
    集合的相关信息
    spring cloud详解
    iostat实时监控磁盘util
    Jenkins安装过程
    hdfs的block为什么设置成128M
    shell变量自增的几种方式
  • 原文地址:https://www.cnblogs.com/lfri/p/11197278.html
Copyright © 2020-2023  润新知