可持久化陣列與可持久化並查集

2022-02-27 08:42:25 字數 3019 閱讀 5888

可持久化陣列:

維護這樣的乙個長度為 \(n\) 的陣列,支援如下幾種操作

在某個歷史版本上修改某乙個位置上的值

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

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

用到主席樹,每一次修改,都會建立乙個新的根節點,代表了乙個新的版本

主席樹結構其實和線段樹比較相近,葉子節點維護的是實際的陣列元素,然後每向上乙個層節點數減少一半

發現每次修改,都只會變動乙個陣列元素,那,在主席樹上,就只變動它到根節點的路徑上的 \(o(\log n)\) 個節點就行了

具體實現方法

現在在樹上遞迴到區間 \([l,r]\),\(pos\) 為要修改的節點的位置,如果 \(pos\le mid\),說明它在左區間,那麼新建乙個節點當左區間,作為當前節點的左兒子

由於右區間完全沒有變化,和之前是一樣的,所以直接讓它指向以前那個版本的右兒子就行了(也就是說乙個節點不一定只有乙個父親,以前那個版本在這乙個區間的右兒子也是現在這個版本在這個區間的右兒子)

然後就遞迴進入左兒子繼續修改

\(pos>mid\) 則相反

這樣,每乙個根節點(版本),也都是乙個結構完整的樹

其實這裡本來想放張圖的,結果畫的好看了就太特殊說明不了啥,稍微普遍一點就很醜很難畫,所以還是自行腦補吧

查詢的時候就從根節點開始一層層遞迴往下找就行了

關於需要的結點數,第0個版本是 \(2n\) 個點,然後每次修改會有 \(\log n\) 個點被新建,所以大概是 \(2n+m\log n\),稍大於 \(2\cdot 10^7\),保險起見直接 \(3\cdot 10^7\) 了

#include#include#include#include#include#include#include#define reg register

#define en std::puts("")

#define ll long long

inline int read()

while(c>='0'&&c<='9')

return y?x:-x;

}struct tr*root[1000005],dizhi[30000005];

int tot,rootcnt;

int n;

int a[1000005];

void build(tr *tree,int l,int r)

void update(tr *tree,tr *old,int l,int r,int pos,int val)

inline void update(int v,int pos,int val)

int get(tr *tree,int l,int r,int pos)

inline int get(int v,int pos)

int main()

return 0;

}

可持久化並查集:

至於為什麼這兩個放在一起說,其實可持久化並查集不就是把原來並查集的陣列換成主席樹嗎。。。

但是這裡不能用路徑壓縮,而是按大小合併

因為用路徑壓縮時,說的是均攤 \(o(\log n)\),也就是每一次操作並不是全都是 \(o(\log n)\) 的,可能有某一次操作的複雜度遠大於此,那麼一加上可持久化,如果不斷執行這一次操作再回到執行前,複雜度就錯誤了

此時每一次合併集合,都需要修改大小小的那個點的 \(fa\) 和大小大的那個點的 \(size\),更改兩個值,所以每次修改生成兩個版本

如果強行和為乙個,記憶體上節省不多(最差情況只省了乙個根節點),但後乙個修改抹掉前乙個修改的值的情況比較難處理,所以意義不大

那麼用 \(num_i\) 記錄第 \(i\) 個版本在第幾個根上

#include#include#include#include#include#include#include#define reg register

#define en std::puts("")

#define ll long long

inline int read()

while(c>='0'&&c<='9')

return y?x:-x;

}struct data;

struct trdizhi[8000005],*root[400005];

int tot,rootcnt;

int n;

int num[200005];

void build(tr *tree,int l,int r),void();

int mid=(l+r)>>1;

tree->ls=&dizhi[++tot];tree->rs=&dizhi[++tot];

build(tree->ls,l,mid);build(tree->rs,mid+1,r);

}data get(tr *tree,int l,int r,int pos)

data find(int k)

void update(tr *tree,tr *old,int l,int r,int pos,data now)

else

}inline void link(int a,int b));

root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[rootcnt-1];

update(root[rootcnt],root[rootcnt-1],1,n,x.x,(data));

} else);

root[++rootcnt]=&dizhi[++tot];*root[rootcnt]=*root[rootcnt-1];

update(root[rootcnt],root[rootcnt-1],1,n,y.x,(data)); }}

int main()

else if(op==2)

else

num[i]=rootcnt;

} return 0;

}

可持久化專題(三) 可持久化並查集

前言 要學可持久化並查集,必須先會可持久化陣列。l in klink link 可持久化陣列詳見部落格可持久化專題 二 可持久化陣列的實現 簡介 可持久化並查集應該是乙個挺實用的資料結構 例如noi2018day1t1中就有它的身影 它主要建立於可持久化陣列的基礎之上 而可持久化陣列的實現是完全基於...

可持久化並查集

n個集合 m個操作 1 a b 合併a,b所在集合 2 k 回到第k次操作之後的狀態 查詢算作操作 3 a b 詢問a,b是否屬於同一集合,是則輸出1否則輸出0 所給的a,b,k均經過加密,加密方法為x x xor lastans,lastans是上一次的輸出答案 並查集實質是乙個陣列,可持久化並查...

可持久化並查集

可持久化陣列 可持久化陣列是一種可以回退,訪問之前版本的陣列 是一些其他可持久化資料結構的基石 例如可持久化並查集 與普通並查集不同的是 這裡用到了 按秩合併新增鏈結描述 include const int n 2e5 7 int rootfa n rootdep n cnt,tot struct ...