1. 程式人生 > 實用技巧 >可持久化線段樹學習筆記

可持久化線段樹學習筆記

可持久化,即對資料修改後仍可查詢到其歷史版本。

以模板題為例:

P3919 【模板】可持久化線段樹 1(可持久化陣列)

  單點修改、查詢的可持久化。

  暴力時空複雜度: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()
{
    
int 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; }
View Code

理解時可以以需求導向(暴力演算法又慢又佔空間)理解

核心:共用記憶體,儘可能少開點(只新建修改的節點)。核心也會適用於一些可持久化線段樹以後的變式