可持久化1——主席樹(可持久化線段樹)


簡介

主席樹就是可持久化線段樹,它的作用就是不停地訪問某個歷史版本,時間復雜度為O((n+m)logn)。

題目

洛谷3919(https://www.luogu.com.cn/problem/P3919)

如題,你需要維護這樣的一個長度為 N 的數組,支持如下幾種操作

  1. 在某個歷史版本上修改某一個位置上的值

  2. 訪問某個歷史版本上的某一位置的值

此外,每進行一次操作(對於操作2,即為生成一個完全一樣的版本,不作任何改動),就會生成一個新的版本。版本編號即為當前操作的編號(從1開始編號,版本0表示初始狀態數組)

分析

先看看暴力做法:每次單點修改一個節點(數組模擬比線段樹少一只log),然后存入一個新的版本里。時間復雜度為O(nm),空間O(nm)。

這顯然在洛谷你會看見一堆黑色(MLE+TLE)

這顯然不可取,那我們有什么更好的做法嗎?

主席樹原理

我們觀察,線段樹的單點修改應該是這樣的,比如我們要修改3號節點

 

我們發現,其實當我們進行線段樹上單點修改時,只會修改紅色路徑上的點,而修改的點是log(n)個,也就是我們要新建log(n)個節點。

 

修改如上圖:

主席樹的一些特性:
1、主席樹的根很多很多,且每個根都有一顆完整的線段樹。

2、每一個節點的父親都不止一個。

操作

接下來,一起收菜(It's show time)。

定義

我們要定義一個結構體,這個結構體需要存三個量,權值val,左兒子lson,右兒子rson。

為什么要存左右兒子?

因為我們這是主席樹,要動態開點,已經不是線段樹的左兒子為根*2,右兒子為根*2+1了。

struct tree{
    int lson,rson,val;
}t[MAXN*20];

 需要注意的是,這里的數組要開N的20倍大小。

建樹

建樹十分簡單,和線段樹基本一樣。注意,這里用了動態開點。

void build(int &rot,int l,int r){//細心的你會發現這個rot是用了取址符的,意味着你只要改了rot,原來你弄進去的變量也會改
    rot=++tot;//動態開店
    if(l==r){
        t[rot].val=n[l];
        return;
    }
    int mid=l+r>>1;
    build(t[rot].lson,l,mid);//這里可以直接寫t[rot].lson,因為用了取址符
    build(t[rot].rson,mid+1,r);
//   t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}

 

修改

修改操作時,我們先把當前節點copy一份,出來的就是上面圖的橙色節點,連的都還是原來的左右兒子。

然后我們判斷我們修改的節點是在左子樹還是在右子樹,然后不停地遞歸下去。

void clone(int &rot,int cl){
    rot=++tot;
    t[rot]=t[cl];
}
void update(int &rot,int l,int r,int cl,int loc,int value){//這里的rot依然用的是有取址符的
    clone(rot,cl);//克隆一份
    if(l==r){
        t[rot].val=value;
        return;
    }
    int mid=l+r>>1;
    if(loc<=mid)update(t[rot].lson,l,mid,t[cl].lson,loc,value);//這里的t[rot].lson和t[cl].lson不要搞錯
    if(loc>mid)update(t[rot].rson,mid+1,r,t[cl].rson,loc,value);//同上
    //   t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}

  

查詢

查詢就很容易了,簡直就是線段樹一樣。

int Find(int rot,int l,int r,int loc){
    if(l==r)return t[rot].val;
    int mid=l+r>>1;
    if(loc<=mid)return Find(t[rot].lson,l,mid,loc);
    else return Find(t[rot].rson,mid+1,r,loc);
}

 

代碼

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000005;
int N,M,n[MAXN],tot,rt[MAXN],v,loc,c,value;
struct tree{
    int lson,rson,val;
}t[MAXN*20];
void build(int &rot,int l,int r){
    rot=++tot;
    if(l==r){
        t[rot].val=n[l];
        return;
    }
    int mid=l+r>>1;
    build(t[rot].lson,l,mid);
    build(t[rot].rson,mid+1,r);
//   t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}
void clone(int &rot,int cl){
    rot=++tot;
    t[rot]=t[cl];
}
void update(int &rot,int l,int r,int cl,int loc,int value){
    clone(rot,cl);
    if(l==r){
        t[rot].val=value;
        return;
    }
    int mid=l+r>>1;
    if(loc<=mid)update(t[rot].lson,l,mid,t[cl].lson,loc,value);
    if(loc>mid)update(t[rot].rson,mid+1,r,t[cl].rson,loc,value);
    //   t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}
int Find(int rot,int l,int r,int loc){
    if(l==r)return t[rot].val;
    int mid=l+r>>1;
    if(loc<=mid)return Find(t[rot].lson,l,mid,loc);
    else return Find(t[rot].rson,mid+1,r,loc);
}
int main(){
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;i++)
        scanf("%d",&n[i]);
    build(rt[0],1,N);
    for(int i=1;i<=M;i++){
        scanf("%d%d%d",&v,&c,&loc);
        if(c==1){
            scanf("%d",&value);
            update(rt[i],1,N,rt[v],loc,value);
        }
        if(c==2){
            printf("%d\n",Find(rt[v],1,N,loc));
            rt[i]=rt[v];
        }
    }
    return 0;
}

  

總結

主席樹還是很容易的,只要你看的懂的話,這是本萌新第一次寫博客,有什么建議可以在下面提,或私聊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM