可持久化線段樹學習筆記
阿新 • • 發佈:2020-11-22
可持久化,即對資料修改後仍可查詢到其歷史版本。
以模板題為例:
單點修改、查詢的可持久化。
暴力時空複雜度:O(nm(版本複製)+m(修改查詢)),可持久化線段樹 時空複雜度只為:O(m log n+n)
題解口胡:
建一個數組hed存各版本的對應的線段樹根
對於修改操作:對修改了的部分進行新建,沒有修改的部分共用。因修改而新建的部分不只有葉子節點,還應有其到根節點的路徑,否則無法維護線段樹的結構。
對於查詢操作:使hed[當前版本數]=hed[查詢版本數],查詢就完事了。(當時還建個新根,連原版本的左右兒子。這都是多此一舉)
陣列大小(本題單點修改):4*n(一棵線段樹)+m log n(每次修改剛好建logn個點)
進過的坑:
呼叫修改或查詢函式裡表區間的l,r形參要為1,n,結果直接寫成l和r了,沒注意意義。
查詢函式裡面的遞迴原樹上的點要跟著走
模板題AC程式碼:
#include<iostream> #include<cstdio> using namespace std; const int N=1e6+6; int n,m,hed[N],num[N*24],ls[N*24],rs[N*24],cnt; inline int read() {View Codeint x=0; bool f=0; char ch=getchar(); while(!isdigit(ch)) f|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); return f?-x:x; } void build(int t,int l,int r) { if(l==r) { num[t]=read(); return; } ls[t]=++cnt; build(cnt,l,(l+r)>>1); rs[t]=++cnt; build(cnt,((l+r)>>1)+1,r); } void modify(int u,int t,int l,int r,int w,int v) { if(l==r) { num[u]=v; return; } if(w<=(l+r)>>1) { rs[u]=rs[t]; ls[u]=++cnt; modify(cnt,ls[t],l,(l+r)>>1,w,v); } else { ls[u]=ls[t]; rs[u]=++cnt; modify(cnt,rs[t],((l+r)>>1)+1,r,w,v); } } int fin(int t,int l,int r,int w) { if(l==r) return num[t]; if(w<=(l+r)>>1) return fin(ls[t],l,(l+r)>>1,w); else return fin(rs[t],((l+r)>>1)+1,r,w); } int main() { n=read(),m=read(); cnt=1; build(1,1,n); hed[0]=1; int t,ord,w,v; for(int i=1;i<=m;++i) { t=read(),ord=read(); if(ord==1) { w=read(),v=read(); hed[i]=++cnt; modify(cnt,hed[t],1,n,w,v); } else { w=read(); hed[i]=hed[t]; printf("%d\n",fin(hed[t],1,n,w)); } } return 0; }
理解時可以以需求導向(暴力演算法又慢又佔空間)理解
核心:共用記憶體,儘可能少開點(只新建修改的節點)。核心也會適用於一些可持久化線段樹以後的變式