栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

主席树模板&可持久化数据结构介绍

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

主席树模板&可持久化数据结构介绍

引言 一、

可持久化数据结构的前提是其拓扑排序是不变的;

比如线段树,我们改变的总是它维护的数值,他的结构一旦定下来就不会变了;

二、

可持久化的核心思想是类似git操作,我们维护版本间的差异,而不是维护整个版本;


主席树

主席树又称可持久化线段树,其持久化的核心思想和引言的一致;

主席树难以进行区间修改操作(当然我们可以通过永久化懒标记实现, 一般碰不上)


也就是我们每修改一个节点,我们就创建一个新的副本,并在副本的基础上进行修改;


如图所示,假设我们修改了 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8这条链,我们就创建这条链的一个备份,并且在这条链上修改;


每个版本我们都认为他是一颗线段树;

因为版本间只有若干节点的不同

因此其他相同节点是各个版本共用的;

空间大小

因为线段树上的修改,每次只会涉及 4 l o g 2 n 4log_2n 4log2​n左右的节点;

假设有 n n n个节点,一般来说空间只需要开 4 ∗ n + n l o g 2 n 4*n+nlog_2n 4∗n+nlog2​n就够了;

如果要保险的话,开 n < < 5 n<<5 n<<5肯定够了…

例题一

第K小数


思路

首先,因为数值比较大,范围比较小,因此我们离散化处理一下;

其次,我们在数值上建立一颗线段树(注意不是区间);

然后遍历数组的每一个元素,每遍历一个元素,我们就创建一个新的版本;

这样我们一共有 n + 1 n+1 n+1个版本(0~n);

我们用 r o o t ( i ) root(i) root(i)表示第 i i i个版本的线段树根节点编号;


询问 [ L , R ] [L,R] [L,R]时,我们用第 R R R个版本的线段树减去第 L − 1 L-1 L−1个版本的线段树;

因为第 i i i个版本的线段树,表示的是 1 到 i 1到i 1到i以来所有改变的值;

因此第 R R R个版本减去第 L − 1 L-1 L−1个版本后;

我们就知道版本 [ L , R ] [L,R] [L,R]改变了什么;

也就是前缀和的思想;


又因为我们是每遍历到一个元素就更新一个版本;

因此求区间 [ L , R ] [L,R] [L,R]就转化为了求版本 [ L , R ] [L,R] [L,R]


如下图所示,比如我们要区间 [ 2 , 4 ] [2,4] [2,4]第二小的数;

第一棵树是版本 4 4 4,第二棵树是版本 1 1 1;


因为数值域 [ 1 , 2 ] [1,2] [1,2]只有一个数, [ 3 , 4 ] [3,4] [3,4]有两个数,因此我们去右子树找;

在右子树的左子树就找到了我们的答案;

Code

注意和普通线段树不同,我们的节点是不维护区间的;

节点中的 l , r l,r l,r是左右子树的坐标;

而我们存树则类似于图的链式前向星存法;

代码中的q代表新版本,p代表旧版本!!!

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

struct Node{
    int lc,rc;//左右子树节点的编号
    int sum;//当前节点里面维护了多少个数
}tr[N*4 + N*17];

vector ve;

//root(i)表示第i个版本的线段树根节点编号
int n,m,root[N],cnt,a[N];

//映射到[1,n]
int _get(int x){
    return 1 + lower_bound(ve.begin(),ve.end(),x) - ve.begin();
}
//在数值上建立线段树
int build(int l,int r){
    int p = ++cnt;
    if(l == r) return p;
    int mid = (l+r) >> 1;
    tr[p].lc = build(l,mid);
    tr[p].rc = build(mid+1,r);
    return p;
}
//更新版本
int update(int p,int l,int r,int val){
    int q = ++cnt;
    tr[q] = tr[p];//先复制上个版本
    if(l == r){
        ++tr[q].sum;
        return q;
    }
    int mid = (l+r) >> 1;
    if(val<=mid) tr[q].lc = update(tr[p].lc,l,mid,val);
    else tr[q].rc = update(tr[p].rc,mid+1,r,val);
    tr[q].sum = tr[tr[q].lc].sum + tr[tr[q].rc].sum;
    return q;
}
//返回第k小的数离散后的坐标
int query(int q,int p,int l,int r,int k){
    if(l == r) return l;
    int sum = tr[tr[q].lc].sum - tr[tr[p].lc].sum;
    int mid = (l+r) >> 1;
    //去左树
    if(k<=sum) return query(tr[q].lc,tr[p].lc,l,mid,k);
    //去右树
    else return query(tr[q].rc,tr[p].rc,mid+1,r,k-sum);
}
int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin >> n >> m;
    for(int i=1;i<=n;++i){
        cin >> a[i];
        ve.push_back(a[i]);
    }
    sort(ve.begin(),ve.end());
    ve.erase(unique(ve.begin(),ve.end()),ve.end());
    root[0] = build(1,ve.size());
    for(int i=1;i<=n;++i){
        root[i] = update(root[i-1],1,ve.size(),_get(a[i]));
    }
    int L,R,k,idx;
    while(m--){
        cin >> L >> R >> k;
        idx = query(root[R],root[L-1],1,ve.size(),k);
        cout << ve[idx-1] << 'n';
    }
    return 0;
}
例题二、 题面

洛谷P3919



思路

这题跟上题不太一样,有点传统线段树维护区间的味道;

上一道题更类似于权值线段树;

线段树节点维护的是左儿子、右儿子、当前节点的值;

因为这道题是类似维护区间,因此我们不需要离散化;

Code
#include 
#include 
#include 

using namespace std;

typedef long long ll;

const int N = 1e6 + 10;

struct Node{
    int lc,rc,val;
}tr[N<<5];

int cnt,n,m,a[N],root[N];

int build(int l,int r){
    int q = ++cnt;
    if(l == r){
        tr[q].val = a[l];
        return q;
    }
    int mid = (l+r) >> 1;
    tr[q].lc = build(l,mid);
    tr[q].rc = build(mid+1,r);
    return q;
}
int update(int p,int l,int r,int loc,int val){
    int q = ++cnt;
    tr[q] = tr[p];
    if(l == r){
        tr[q].val = val;
        return q;
    }
    int mid = (l+r) >> 1;
    if(loc <= mid) tr[q].lc = update(tr[p].lc,l,mid,loc,val);
    else tr[q].rc = update(tr[p].rc,mid+1,r,loc,val);
    return q;
}
int query(int p,int l,int r,int loc){
    if(l == r){
        return tr[p].val;
    }
    int mid = (l+r) >> 1;
    if(loc <= mid) return query(tr[p].lc,l,mid,loc);
    else return query(tr[p].rc,mid+1,r,loc);
}
int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin >> n >> m;
    for(int i=1;i<=n;++i){
        cin >> a[i];
    }
    root[0] = build(1,n);
    for(int i=1,v,op,loc,val;i<=m;++i){
        cin >> v >> op;
        if(op == 1){
            cin >> loc >> val;
            root[i] = update(root[v],1,n,loc,val);
        }else{
            cin >> loc;
            root[i] = root[v];
            cout << query(root[v],1,n,loc) << 'n';
        }
    }
    return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/294256.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号