在這里,所謂“可持久化”的數據結構并非指將數據存在非易失的存儲器上,而是指保存了數據修改的歷史信息。比如說對可持久化線段樹進行修改操作,操作完成后我們可以在線段樹原有的時間復雜度內查詢到希望查詢的版本的信息,比如“第二次修改后區間L和R之間的和”。
通常遇到的線段樹都是構建之后結構不變化的,所以在修改關鍵值時,只有節點內的值受到影響,而樹本身的結構不發生變化(比如左右子節點所表示的區間)。這為線段樹進行可持久化提供了便利。我們每次修改的時候不直接改動原來節點的值,而是創建一系列新的節點。如果整棵樹復制的話不僅非常耗費時間,而且占用空間太大。在線段樹的單次修改中,實際上受到影響的節點是有限的,原來的節點可以得到重復利用。
修改的過程
可持久化線段樹每次修改都會自上而下地新建一些節點。每次修改后的版本都有一個根節點與之對應。
只考慮單點修改,我們將遞歸過程中所有的節點創建一個“影子節點”,所謂“影子節點”保存的是當前修改結束后的受到更改的值。當u是v的影子節點時,我們稱v時u的原節點。
在線段樹的修改操作中,子節點修改完成后只影響到父節點(pushup操作),而不會影響到兄弟節點。所以我們發現,當修改影響到非葉子結點u時,(在單點修改中)他一定只有一個子節點會受到修改的影響,比如右子節點受到影響,此時u的影子節點v的左子節點指向u的左子節點,而v的右子節點對應的是受到影響的新右子節點w,w是u的右子節點的影子節點,以此類推。
Lazy標簽
對于區間修改,需要維護一個lazy標簽來推遲更新操作,在pushdown操作時,創建了u的原節點的子節點的影子節點,在實際實現中,通過維護一個節點的origin節點指針就可以做到這一點。
實現
由于可持久化線段樹在修改過程中需要不斷新建影子節點,所以通常的下標標記子節點的方法不再有效。節點需要維護的不僅僅是線段樹的關鍵值x,還有左右子節點指針lch、rch,lazy標記和原節點指針origin。
在修改線段樹時,沿著最新版本的線段樹自上而下地遍歷、創建影子節點并修改即可。
下面的程序實現了一個維護區間和的可持久化線段樹,支持區間修改。而且這個程序包含了一個demo。首先輸入一個n,隨后輸入n個整數,表示a[1]~a[n]的初始值。隨后開始查詢和修改操作,輸入q查詢,m修改。查詢接受一個版本號(從0開始),輸出序列所有的值。修改接受u、v和w,表示將區間[u,v]每個元素加上w。
#include <bits/stdc++.h>
using namespace std;
// 可持久化線段樹
const int N = 100010;
struct Node {
int sum, lch, rch, lazy, origin;
Node():sum(0),lch(-1),rch(-1),lazy(0),origin(-1) {}
}tree[(N<<2)*4];
int tot, a[N], rt[N], curver;
void init() { tot=0; curver=0; }
int createIndentity(int p) { // 創建影子節點
int cp=tot++;
tree[cp]=Node();
tree[cp].origin=p;tree[cp].sum=tree[p].sum;tree[cp].lazy=tree[p].lazy;
return cp;
}
void pushup(int p) { tree[p].sum=tree[tree[p].lch].sum+tree[tree[p].rch].sum; }
void pushdown(int p,int l,int r,int m) {
int lch=tree[p].lch, rch=tree[p].rch;
if (lch==-1||rch==-1) {
int o=tree[p].origin;
lch=tree[p].lch=createIndentity(tree[o].lch);
rch=tree[p].rch=createIndentity(tree[o].rch);
}
tree[lch].lazy+=tree[p].lazy; tree[rch].lazy+=tree[p].lazy;
tree[lch].sum+=tree[p].lazy*(m-l+1); tree[rch].sum+=tree[p].lazy*(r-m);
tree[p].lazy=0;
}
int build(int l, int r) {
int p=tot++;
tree[p]=Node();
tree[p].sum=a[l];
if (l==r) return p;
int mid=l+r>>1;
tree[p].lch=build(l,mid);
tree[p].rch=build(mid+1,r);
pushup(p);
return p;
}
int add(int p, int l, int r, int x, int y, int z) {
int cp=tot++; // create shadow node
tree[cp]=Node();
tree[cp].origin=p; // origin node number, prepared for pushdown operation
if (x<=l&&r<=y){
tree[cp].lazy=tree[p].lazy+z;
tree[cp].sum=tree[p].sum+z*(r-l+1);
return cp;
}
int mid=l+r>>1;
if (tree[p].lazy) pushdown(p,l,r,mid);
if (x<=mid)
tree[cp].lch=add(tree[p].lch,l,mid,x,y,z);
else tree[cp].lch=tree[p].lch;
if (mid<y)
tree[cp].rch=add(tree[p].rch,mid+1,r,x,y,z);
else tree[cp].rch=tree[p].rch;
pushup(cp);
return cp;
}
int query(int p, int l, int r, int x, int y) {
if (x<=l&&r<=y) return tree[p].sum;
int mid=l+r>>1, ret=0;
if (tree[p].lazy) pushdown(p,l,r,mid);
if (x<=mid) ret+=query(tree[p].lch,l,mid,x,y);
if (mid<y) ret+=query(tree[p].rch,mid+1,r,x,y);
return ret;
}
int main() {
int n;
scanf("%d", &n);
for (int i=1;i<=n;++i) scanf("%d", a+i);
//
init();
rt[curver]=build(1,n);
for (;;){
int u,v,w;
char q[3];
printf("q/m:");
scanf("%s", q);
if (q[0]=='m') {
printf("Please input u, v, w and we will add w to [u,v]: ");
scanf("%d%d%d", &u, &v, &w);
rt[curver+1]=add(rt[curver],1,n,u,v,w);
++curver;
for (int i=1;i<=n;++i) {
printf("%d ", query(rt[curver],1,n,i,i));
}
puts("");
}else {
printf("Please input ver: ");
scanf("%d", &w);
for (int i=1;i<=n;++i) {`
printf("%d ", query(rt[w],1,n,i,i));
}
puts("");
}
}
return 0;
}
Have fun!