zoukankan      html  css  js  c++  java
  • 『主席树』可持久化线段树模板

    学习可持久化线段树前提须知

    可持久化 是一种数据结构的统称,他们同样有的功能就是能够 保存历史版本 ,今天我们要说的可持久化线段树就是这样一种数据结构,能够保存历史版本

    算法内容

    竞赛需要用到的点

    1、可持久化线段树的空间消耗较大,使用时注意一下数据范围

    2、可持久化线段树对于区间问题都能够解决,基本形态类似但是代码不同,需要熟练掌握主席树的原理与代码含义

    可持久化线段树求第k大问题略讲

    可持久化线段树可以维护很多东西,我们这里挑个比较常见的来讲,(该算法对于我这种人来说还是比较难,请观看人自己权衡)。考虑这样一个问题,每次修改都会保留一个时间戳,然后每次询问我们都会访问某个时间戳上的某个区间问题,很显然,正常的线段树并不能满足我们的需求,我们来考虑可持久化写法,若为单点修改,那么每次修改只会改变(logn)个节点,换个思路,修改就相当于我们可以多开(logn)个节点来保存修改之后的这些值,因为其他节点不变,所以我们可以共用这些节点。

    共用节点?什么意思,可以参考下图(洛谷题解摘,实在是不想自己画了)

    定义:橙色是改变前,蓝色是改变后 但仅有[1,4] [3,4] [4,4]为新建的点

    我们发现,橙色的[1,4]和蓝色的[1,4]共用[1,2]这样的点,[1,2]的子树当然也不用多说,这样就能完成我们的可持久化操作了,但这样还有一个问题就是,怎么知道它们每个版本的情况怎么样?我们需要用一个数组保存节点个数,这样我们就能轻松反馈一个版本的情况了

    解决完这些情况后,我们来考虑这道题,在分析第(k)大前我们需要在考虑一个东西就是,在([l, r])区间中找第(k)大,这个([l, r])区间怎么办,当你分析到这里的时候,其实答案也就呼之欲出了,以前缀和维护区间内容,什么意思?我们以每个叶子节点为一个数,每个区间为含有的数的个数作一棵权值线段树,那么就有,每一个含第一个数的前缀和就是一个版本,这样([1, l])就是一棵可持久化线段树,继而([1, r])也是一棵可持久化线段树,哎 因为我们用的前缀和进行建树,是不是每次版本也满足前缀和的性质呢?当然满足,也就是说我们想求([l, r])区间的第(k)大,我们可以用([1.r] - [1, l - 1]),这样便是我们的([l, r])

    满足了区间要求,我们再来满足我们的第(k)大要求,现在我们假设已经求出了([l,r])这个版本的权值线段树了,求第(k)大,我们考虑左子树,发现数量多于等于(k),很明显我们的答案在左边。若少于(k),很显然我们的答案在右侧,就往右边走,但要注意,往右边走时我们的第(k)大就变成了 (k) - 左子树(([1, mid]))所包含的数,这个可以自己画图理解

    到此,我们就可以写出代码了

    求第k大可持久化线段树部分代码展现

    和线段树一样,有个建树

    //#define fre yes
    
    #include <cstdio>
    
    const int N = 1000005;
    int T[N];
    struct Node {
        int l, r, interval;
    } tree[N << 5];
    
    int cnt; //点的个数
    void build(int l, int r) {
        int rt = ++cnt;
        tree[rt].l = l;
        tree[rt].r = r;
        tree[rt].interval = 0;
        if(l == r) return ;
        
        int mid = (l + r) >> 1;
        build(l, mid);
        build(mid + 1, r);
    }
    
    int main() {
        build(1, m);
    }
    

    然后单点修改

    int update(int pre, int l, int r, int x) { //update(T[g], 1, m, x) //g代表哪次版本
        int rt = ++cnt;
        tree[rt].l = tree[pre].l;
        tree[rt].r = tree[prt].r;
        tree[rt].interval = tree[pre].interval;
        if(l == r) return rt;
        int mid = (l + r) >> 1;
        if(mid >= x) tree[rt].l = (tree[pre].l, l, mid, x);
        else tree[rt].r = (tree[pre].r, mid + 1, r, x);
        return rt;
    }
    

    然后单点查询

    int query(int u, int v, int l, int r, int x) {
        if(l == r) return l;
        int t = tree[tree[v].l].sum - tree[tree[u].l].sum;
    	int mid = (l + r) >> 1;
        if(t >= x) return query(tree[u].l, tree[v].l, l, mid, x);
        else return query(tree[u].r, tree[v].r, mid + 1, r, x - t);
    }
    

    最后放一道例题 洛谷P3834

    完整代码

    //#define fre yes
    
    #include <cstdio>
    #include <algorithm>
    
    const int N = 200005;
    int a[N], b[N], T[N];
    
    struct Node {
        int l, r, sum;
    } tree[N << 5];
    
    int cnt;
    void build(int l, int r) {
        int rt = ++cnt;
        tree[rt].l = l;
        tree[rt].r = r;
        tree[rt].sum = 0;
        if(l == r) return ;
        int mid = (l + r) >> 1;
        build(l, mid);
        build(mid + 1, r);
    }
    
    int update(int pre, int l, int r, int x) {
        int rt = ++cnt;
        tree[rt].l = tree[pre].l;
        tree[rt].r = tree[pre].r;
        tree[rt].sum = tree[pre].sum + 1;
        if(l == r) return rt;
        int mid = (l + r) >> 1;
        if(mid >= x) tree[rt].l = update(tree[pre].l, l, mid, x);
        else tree[rt].r = update(tree[pre].r, mid + 1, r, x);
        return rt;
    }
    
    int query(int u, int v, int l, int r, int x) {
        if(l == r) return l;
        int sum = tree[tree[v].l].sum - tree[tree[u].l].sum;
        int mid = (l + r) >> 1;
        if(sum >= x) return query(tree[u].l, tree[v].l, l, mid, x);
        else return query(tree[u].r, tree[v].r, mid + 1, r, x - sum);
    }
    
    int main() {
        static int n, q;
        scanf("%d %d", &n, &q);
        for (int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &a[i]);
            b[i] = a[i];
        }
        std::sort(b + 1, b + 1 + n);
        int m = std::unique(b + 1, b + 1 + n) - b - 1;
    
        T[0] = 0;
        build(1, m);
        for (int i = 1; i <= n; i++) {
            int t = std::lower_bound(b + 1, b + 1 + m, a[i]) - b;
            T[i] = update(T[i - 1], 1, m, t);
        }
    
        for (int i = 1; i <= q; i++) {
            int x, y, z;
            scanf("%d %d %d", &x, &y, &z);
            int t = query(T[x - 1], T[y], 1, m, z);
            printf("%d
    ", b[t]);
        } return 0;
    }
    
  • 相关阅读:
    eclipse中集成python开发环境
    取消eclipse英文单词拼写验证
    exe所在路径
    [转]关于Megatops BinCalc RPN计算器的说明
    WinDbg 蓝屏dump分析教程
    Delphi与Windows 7下的用户账户控制(UAC)机制 及 禁用兼容性助手
    【Delphi7】 解决“程序第一次可以正常编译,但再次编译的时候会报错,必须重新打开Delphi”的问题
    解决xftp远程连接中文乱码
    创建用资源管理器打开FTP位置
    收藏夹里的js
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11484903.html
Copyright © 2011-2022 走看看