• 可持久化数据结构


    可持久化数据结构

    可持久化线段树

    据说这个东西是(hjt)发明的(不是国家领导人

    由于上句所述原因,又称主席树.
    这个东西有啥用呢?
    支持历史版本的查询.
    这有啥用?出题考你
    实现历史版本查询的一个朴素想法是:
    对于每一个版本都建一棵线段树,开桶记录根节点,每次对应查询即可.
    这样的正确性是显然的,时间复杂度也是对的.
    那为什么还要可持久化线段树呢?
    因为朴素的做法的空间复杂度(Theta(4n imes m))或者(Theta(2n imes m)).
    其中(n)是数据规模,(m)是版本个数,(2n)(4n)取决于线段树写法.
    那么,可持久化线段树的时空复杂度如何呢?时间复杂度当然不会更劣.
    空间复杂度大概在(Theta(2n+m imes log_2{n}))(Theta(4n+m imes log_2{n})).各字母含义与上述相同.
    和上面相比相当的优秀.为什么呢?
    因为我们对于每一个版本都只需要新建至多(log_2{n})个节点.
    上图:
    这个亚子
    一棵构建好的主席树也就差不多这样.
    它避免了重建整棵树,而是选择把原树中相对于新版本没有改变的节点加以利用,从而达到了节省空间的目的.
    然后你可能会说这不是一棵树,因为它每个节点不只有一个父亲(看起来
    但其实不是,因为主席树其实是由若干棵部分空间共享的线段树组成的,图上多出来的父亲关系其实是另一棵线段树的结构,而不属于本棵线段树,因此你在访问的时候实际访问到的是一棵正常的线段树.
    那么它这样的结构如何访问呢?
    根节点还是开桶记录,(如果桶开不下说明这题显然不可用主席树做
    这个时候,其实我们会发现,这一组线段树其实是一个前缀和的形式.
    所以它遵循前缀和的规则.
    其他的地方其实和普通线段树一般无二.
    经典例题:静态区间第k大
    首先肯定要离散化+权值线段树的.
    这里我们的主席树是可持久化权值线段树.
    每一棵树表示一个前缀的权值信息.
    查询的时候把对应区间的两棵线段树相减再去二分地找即可.

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <string>
    #include <vector>
    #include <queue>
    #include <cmath>
    #include <ctime>
    #include <map>
    #include <set>
    #define MEM(x,y) memset ( x , y , sizeof ( x ) )
    #define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
    #define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
    #define pii pair < int , int >
    #define one first
    #define two second
    #define rint read<int>
    #define int long long
    #define pb push_back
    #define db double
    
    using std::queue ;
    using std::set ;
    using std::pair ;
    using std::max ;
    using std::min ;
    using std::priority_queue ;
    using std::vector ;
    using std::swap ;
    using std::sort ;
    using std::unique ;
    using std::greater ;
    
    template < class T >
        inline T read () {
            T x = 0 , f = 1 ; char ch = getchar () ;
            while ( ch < '0' || ch > '9' ) {
                if ( ch == '-' ) f = - 1 ;
                ch = getchar () ;
            }
           while ( ch >= '0' && ch <= '9' ) {
                x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
                ch = getchar () ;
           }
           return f * x ;
    }
    
    const int N = 2e5 + 100 ;
    
    int n , m , v[N] , w[N] ;
    
    class Chairman {
        #define mid ( ( l + r ) >> 1 )
        private :
            struct seg { int lson , rson , data ; } t[N*20] ;
        private : int cnt ;
        public : int rt[N] ;
        public :
            inline void initial () { cnt = 0 ; return ; }
        private :
            inline void pushup (int rt) { t[rt].data = t[t[rt].lson].data + t[t[rt].rson].data ; }
        public :
            inline void insert (int & rt , int l , int r , int key) {
                t[++cnt] = t[rt] ; rt = cnt ;
                if ( l == r ) { ++ t[rt].data ; return ; }
                if ( key <= mid ) insert ( t[rt].lson , l , mid , key ) ;
                else insert ( t[rt].rson , mid + 1 , r , key ) ;
                pushup ( rt ) ; return ;
            }
        public :
            inline int query (int u , int v , int l , int r , int key) {
                if ( l == r ) return l ;
                int T = t[t[v].lson].data - t[t[u].lson].data ;
                if ( key <= T ) return query ( t[u].lson , t[v].lson , l , mid , key ) ;
                else return query ( t[u].rson , t[v].rson , mid + 1 , r , key - T ) ;
            }
        #undef mid
    } T ;
    
    signed main (int argc , char * argv[]) {
        n = rint () ; m = rint () ; T.initial () ;
        rep ( i , 1 , n ) w[i] = v[i] = rint () ;
        sort ( w + 1 , w + n + 1 ) ; w[0] = unique ( w + 1 , w + n + 1 ) - w - 1 ;
        rep ( i , 1 , n ) v[i] = std::lower_bound ( w + 1 , w + w[0] + 1 , v[i] ) - w ;
        T.rt[0] = 0 ; rep ( i , 1 , n ) T.rt[i] = T.rt[i-1] , T.insert ( T.rt[i] , 1 , w[0] , v[i] ) ;
        rep ( i , 1 , m ) {
            int l = rint () , r = rint () , k = rint () ;
            printf ("%lld
    " , w[T.query ( T.rt[l-1] , T.rt[r] , 1 , w[0] , k )] ) ;
        }
        system ("pause") ; return 0 ;
    }
    

    可持久化数组

    可持久化数组是可持久化并查集的基础,如果要学习可持久化并查集,该数据结构是绕不过去的门槛.
    可持久化数组的经典操作在下面的模板题中已经给出:
    模板:可持久化数组
    这里的版本就比较明显了.
    而且这显然可以使用主席树去维护.
    直接按照题意用主席树模拟即可.
    只需要单点修改,单点查询.
    (注:可持久化数组并非只能用可持久化线段树实现,其他数据结构例如可持久化(Treap)也是可以是实现的,据说还有一个不依赖于其他数据结构的真·可持久化数组写法,但笔者并不会.)

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <string>
    #include <vector>
    #include <queue>
    #include <cmath>
    #include <ctime>
    #include <map>
    #include <set>
    #define MEM(x,y) memset ( x , y , sizeof ( x ) )
    #define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
    #define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
    #define pii pair < int , int >
    #define one first
    #define two second
    #define rint read<int>
    #define int long long
    #define pb push_back
    #define db double
    
    using std::queue ;
    using std::set ;
    using std::pair ;
    using std::max ;
    using std::min ;
    using std::priority_queue ;
    using std::vector ;
    using std::swap ;
    using std::sort ;
    using std::unique ;
    using std::greater ;
    
    template < class T >
        inline T read () {
            T x = 0 , f = 1 ; char ch = getchar () ;
            while ( ch < '0' || ch > '9' ) {
                if ( ch == '-' ) f = - 1 ;
                ch = getchar () ;
            }
           while ( ch >= '0' && ch <= '9' ) {
                x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
                ch = getchar () ;
           }
           return f * x ;
    }
    
    const int N = 1e6 + 100 ;
    
    int n , m , v[N] , now ;
    
    class Chairman {
        #define mid ( ( l + r ) >> 1 )
        public : int rt[N] ;
        private : int cnt ;
        private : struct seg { int lson , rson , data ; } t[N*20] ;
        public : inline void initial () { cnt = 1 ; return ; }
        public :
            inline void build (int rt , int l , int r) {
                if ( l == r ) { t[rt].data = v[l] ; return ; }
                t[rt].lson = ++ cnt ; build ( t[rt].lson , l , mid ) ;
                t[rt].rson = ++ cnt ; build ( t[rt].rson , mid + 1 , r ) ;
                return ;
            }
        public :
            inline void update (int & rt , int l , int r , int pos , int key) {
                t[++cnt] = t[rt] ; rt = cnt ;
                if ( l == r ) { t[rt].data = key ; return ; }
                if ( pos <= mid ) update ( t[rt].lson , l , mid , pos , key ) ;
                else update ( t[rt].rson , mid + 1 , r , pos , key ) ;
                return ;
            }
        public :
            inline int query (int rt , int l , int r , int pos) {
                if ( l == r ) return t[rt].data ;
                if ( pos <= mid ) return query ( t[rt].lson , l , mid , pos ) ;
                else return query ( t[rt].rson , mid + 1 , r , pos ) ;
            }
        #undef mid
    } T ;
    
    signed main (int argc , char * argv[]) {
        n = rint () ; m = rint () ; T.initial () ;
        rep ( i , 1 , n ) v[i] = rint () ;
        T.build ( 1 , 1 , n ) ; T.rt[0] = 1 ;
        while ( m -- ) {
            int k = rint () , opt = rint () , x = rint () ;
            if ( opt == 1 ) {
                int y = rint () ; int tmp = T.rt[k] ;
                T.update ( tmp , 1 , n , x , y ) ;
                T.rt[++now] = tmp ;
            }
            if ( opt == 2 ) {
                T.rt[++now] = T.rt[k] ;
                printf ("%lld
    " , T.query ( T.rt[k] , 1 , n , x ) ) ;
            }
        }
        system ("pause") ; return 0 ;
    }
    
  • 相关阅读:
    Django 之 ORM
    python 变量问题 提问有大佬知道吗?
    celery 使用详解
    ubuntu 网络图标消失问题解决
    django + uwsgi 部署上线
    Python pip常用源地址
    ConfigParser 模块 使用
    Mysql 远程登录问题解决
    商城项目 从设想到实现
    硬盘上有打开的锁和感叹号标志如何解决(win10系统)
  • 原文地址:https://www.cnblogs.com/Equinox-Flower/p/11649912.html
Copyright © 2020-2023  润新知