可持久化線段樹學習筆記

2022-08-24 23:06:11 字數 4486 閱讀 7863

可持久化是真的毒瘤,在網上找了很多資料才搞懂

(不過我覺得應該是我太蒻了)

首先以洛谷上的兩個板子題為例吧:

對於第一題,要求詢問區間第k大(第k大指的是從小到大排序的第k個),直接掃是肯定不行的,因此我們需要可持久化線段樹(感覺跳的好快,但是我也不清楚要怎麼表達,就這樣吧,反正知道是要用這個就行了)

首先既然是可持久化線段樹,那麼肯定需要用到線段樹的,但是對於一般的線段樹,每個節點維護的是對應區間的最值或者是和積,用這種線段樹寫可持久化我覺得是肯定不行的(emmm具體怎麼樣我也不清楚)

因此我們維護的是屬於對應區間的元素的個數,建樹**如下,要注意開始需要排序後離散化:

struct treenode[5000001];  //用結構體存樹節點的左孩子,右孩子和當前的權值 

int tot_,a[5000001],b[5000001],n;

int build_ (int l,int r)

return now;

}void makepoint_ (int now,int l,int r,int v)

int getnum_ (int k)

return 0;

}int main ()

return 0;

}

假設給定一串串行a=,那麼構建出來的線段樹應該是這樣的(節點上為當前區間的元素個數,節點旁邊分別為節點編號以及維護的區間):

這裡維護的是 [1,4] 的區間,那麼如果我們要求這個區間的第3大的值,令 l=1 r=4 先將3與根節點的左子樹的權值比較,左子樹存的是 [1,2] 區間的值的個數為2,因為3>2,所以我們所求值不能在左子樹中找(總共只有2個數要求第3大的數再怎麼也求不出吧xd)

接著看向右子樹,右子樹儲存的是 [3,4] 區間個元素個數也為2,那麼是不是找不到了呢?再想一想,我們應該在右子樹找的應該是第3-2=1大的值(這個應該不難理解吧,用所求的第k大的值減去左子樹的權值),因為1<=2,所以繼續從右子樹向下搜,l變成中間值 (l+r)/2+1 r不變 重複這個過程直到 l=r ,那麼此時的 l 為所求的元素離散化後的值

**如下:

int query_ (int tree,int l,int r,int k)
但是這樣顯然是不夠的,我們只能求出 [1,4] 區間的第k大,而題目要求是任意區間,我們總不能對每一種可能的區間都進行建樹,因此我們考慮運用字首和的方法,只需要再建立維護 [1,1] , [1,2] , [1,3] 區間的線段樹

例如再建一顆維護 [1,1] 區間的線段樹:

為了方便我再把之前那張線段樹的圖貼出來

我們觀察根節點的權值,發現權值差正好是 [2,4] 區間內元素個數,再看其左子樹,差為2,那麼這個2表示什麼呢?再看看原數列, [2,4] 區間元素為,其中大小(或者說離散化後的編號)在 [1,2] 區間內的元素正好是2個,再對比其他節點的權值,我們可以得出,對於兩個分別維護區間 [1,i] 和區間 [1,j] (0<=i但是對於乙個長度為n的序列建立n個線段樹也並不現實,因此我們需要繼續優化

再看之前的維護 [1,1] 的線段樹,若我們插入乙個節點,即使其維護區間 [1,2]

這時得到的線段樹為:

可以發現,被修改的實際上只有一條鏈:

繼續插入也是一樣,只在前一顆樹的基礎上修改了一條鏈的權值,因此我們不需要構建那麼多個線段樹,只需要構建一棵樹再在樹上增加鏈就行了,也就成了我們需要的可持久化線段樹:

接下來是構建過程:

先建造乙個空樹

插入第乙個節點(紅色表示新增部分,為了簡便沒有註明節點編號與維護區間,而且很小請見諒):

每次新建一條鏈,並與上一棵樹連線(對於每個節點的兩個子節點,若該數大小在左子樹區間內則新建左子樹,左子樹先預設與前一顆樹當前位置左子樹狀態相同(狀態相同即結構體內的 left,right,size 相同),然後進行加權值,右子樹為前一棵樹相同位置的右子樹,否則新建右子樹)接下來也是一樣

此時我們就得到了維護區間 [1,4] 的可持久化線段樹,如果以右上權值為4的那個節點為根節點來看,與之前得到維護 [1,4] 的線段樹是一樣的

因為現在是一顆可持久化線段樹,因此操作與之前的也有略微區別,我們先定義乙個陣列 root root[i] 表示第 i 條鏈的根節點,也就是第 i 條線段樹,再修改之前的建樹以及查詢函式:

**實現:

#include #define re register

#define il inline

using namespace std;

struct treenode[5000001]; //用結構體存樹節點的左孩子,右孩子和當前的權值

int tot_,a[5000001],b[5000001],n,m,root_[5000001],q;

il int build_ (int l,int r)

return now;

}il int makepoint (int before,int l,int r,int v)

return now;

}il int query_ (int treea,int treeb,int l,int r,int k)

il int getnum_ (int k)

return o;

}int main ()

for(re int i=1;i<=q;++i)

return o;

}

接下來看一下第二道板子題,題目要求對歷史版本進行修改與查詢,再看一下上一題中我們建的樹:

單獨看最上面一排的五個節點,其實可以發現以每個節點為根形成的線段樹都是乙個歷史版本(這個歷史就是我們按數列插入節點),那麼對於這一題,我們不需要維護區間值個數,只需要在葉節點插入對應位置的值即可,然後對於每次修改,有影響的也只有對應位置的乙個值,我們也只需要構建一條鏈,所以大體思路是和上一題一樣的,直接上**吧:

#include #define ll long long

#define re register

#define il inline

using namespace std;

struct treenode[1000001];

ll tot_,root_[1000001],n,m,a[1000001];

il ll build_ (ll l,ll r)

else

return now;

}il ll makepoint_ (ll tree,ll l,ll r,ll i,ll k)

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

if (i<=mid) node[now].left=makepoint_ (node[tree].left,l,mid,i,k); //如果需要修改的節點位置小於mid

//則新建左子樹

else node[now].right=makepoint_ (node[tree].right,mid+1,r,i,k); //否則新建右子樹

return now;

}il ll qurey_ (ll tree,ll l,ll r,ll k)

int main ()

else

} return 0;

}

那麼大概就這樣吧,我也不清楚還要講些什麼了,總之我認為可持久化線段樹的思路就是通過以建鏈代替建樹達到建立歷史版本的需求,因為有大量的重複節點,這樣可以節省空間和時間。總之就這樣了,我還只會寫模板,還不會具體的應用,這篇文章如果有什麼問題歡迎大佬指出。。。

可持久化線段樹學習筆記

模板 includeusing namespace std const int n 2e5 10 int cnt,rt n int n,a n t n t int ls n 20 rs n 20 dat n 20 void copy int x,int y int build int l,int r...

學習筆記 可持久化線段樹

例題 luogu 3919 特點 在普通線段樹支援查詢當前狀態基礎上,支援查詢過去的所有版本 一 text 暴力儲存過去版本,每一次修改都會造成 nlogn 的時空複雜度,總複雜度 o mnlogn 直接上天 注意到每一次單點修改只會導致對應的葉子節點以及它的所有祖先儲存的值改變,我們可以考慮只儲存...

可持久化線段樹學習筆記

可持久化,即對資料修改後仍可查詢到其歷史版本。以模板題為例 p3919 模板 可持久化線段樹 1 可持久化陣列 單點修改 查詢的可持久化。暴力時空複雜度 o nm 版本複製 m 修改查詢 可持久化線段樹 時空複雜度只為 o m log n n 題解口胡 建乙個陣列hed存各版本的對應的線段樹根 對於...