码迷,mamicode.com
首页 > 其他好文 > 详细

可持久化数据结构

时间:2019-10-10 20:22:36      阅读:105      评论:0      收藏:0      [点我收藏+]

标签:getchar   pair   sig   class   不可用   main   权值线段树   ax1   说明   

可持久化数据结构

可持久化线段树

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

由于上句所述原因,又称主席树.
这个东西有啥用呢?
支持历史版本的查询.
这有啥用?出题考你
实现历史版本查询的一个朴素想法是:
对于每一个版本都建一棵线段树,开桶记录根节点,每次对应查询即可.
这样的正确性是显然的,时间复杂度也是对的.
那为什么还要可持久化线段树呢?
因为朴素的做法的空间复杂度\(\Theta(4n\times m)\)或者\(\Theta(2n\times m)\).
其中\(n\)是数据规模,\(m\)是版本个数,\(2n\)\(4n\)取决于线段树写法.
那么,可持久化线段树的时空复杂度如何呢?时间复杂度当然不会更劣.
空间复杂度大概在\(\Theta(2n+m\times log_2{n})\)\(\Theta(4n+m\times 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\n" , 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\n" , T.query ( T.rt[k] , 1 , n , x ) ) ;
        }
    }
    system ("pause") ; return 0 ;
}

可持久化数据结构

标签:getchar   pair   sig   class   不可用   main   权值线段树   ax1   说明   

原文地址:https://www.cnblogs.com/Equinox-Flower/p/11649912.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!