主席樹詳解

2022-04-30 13:24:11 字數 4020 閱讀 4558

題目給你乙個序列,每次修改後算乙個新的版本,詢問某個版本中某個值

我們先以luogu p3919 【模板】可持久化陣列(可持久化線段樹/平衡樹)作為模板講一下主席樹先學一下線段樹qaq

主席樹本名可持久化線段樹,也就是說,主席樹是基於線段樹發展而來的一種資料結構。其字首"可持久化"意在給線段樹增加一些歷史點來維護歷史資料,使得我們能在較短時間內查詢歷史資料

不同於普通線段樹的是主席樹的左右子樹節點編號並不能夠用計算得到,所以我們需要記錄下來,但是對應的區間還是沒問題的。

我們注意到,對於修改操作,當前版本與它的前驅版本相比,只更改了乙個節點的值,其他大多數節點的值沒有變化。

能不能重複利用,以達到節省空間的目的?

——分治?沒錯,如果只修改了左半邊,那麼我們可以使用前驅版本的右半邊,反之同理。

於是,我們就可以用線段樹,進行修改操作時,只要當前節點的左(右)兒子沒有被修改,我們就可以使用前驅版本的那個節點。

那查詢呢?每次儲存版本i的根節點,利用線段樹的方法查詢就好了。

**實現(**中有詳細注釋qaq):

#include #define n 1000005

using namespace std;

inline char nc()

inline int read()

while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();

return x*f;

}inline void write(register int x)

struct node

int mid=l+r>>1;

ls[root]=build(l,mid),rs[root]=build(mid+1,r);

return root;

} inline int update(register int pre,register int l,register int r,register int x,register int c)

ls[root]=ls[pre],rs[root]=rs[pre];//先把子節點指向前驅結點以備復用

int mid=l+r>>1;

if(x<=mid)

ls[root]=update(ls[pre],l,mid,x,c);

else

rs[root]=update(rs[pre],mid+1,r,x,c);

return root;

} inline void query(register int pre,register int l,register int r,register int x)

int mid=l+r>>1;

if(x<=mid)

query(ls[pre],l,mid,x);

else

query(rs[pre],mid+1,r,x);

}}tr;

int main()

else

}return 0;

}

還有一種問題是求靜態區間[l,r]中第k小的數

先給乙個很暴力的做法:

先將區間進行排序(莫隊),再用平衡樹來求區間第k小

這樣的複雜度是 \(o(n \sqrt n \log n)\)

50分莫隊+平衡樹做法

#pragma gcc optimize("o3")

#include #define n 500005

#define m 200005

using namespace std;

inline char nc()

inline int read()

while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();

return x*f;

}inline void write(register int x)

struct splaytree[n];

int tot=0;

inline void update(register int x)

inline bool findd(register int x)

inline void connect(register int x,register int fa,register int son)

inline void rotate(register int x)

inline void splay(register int x,register int to) }

inline int newpoint(register int v,register int fa)

inline void insert(register int x)

else

int nxt=x1)

else

else

}}inline int arank(register int x)

if(xb.r);

}int main()

; }

sort(q+1,q+m+1,cmp);

int l=1,r=0;

for(register int i=1;i<=m;++i)

inline int read()

while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();

return x*f;

}inline void write(register int x)

int n,q,m,cnt=0;

int a[n],b[n],t[n];

int sum[n<<5],ls[n<<5],rs[n<<5];

inline int build(register int l,register int r)

int main()

while(q--)

}

但是要注意,主席樹在不做額外處理時只能查詢靜態的區間k大(小)值。

接下來,我們就考慮動態區間k小值。如果我們要對區間進行修改的話,乙個簡單的主席樹已經無法實現了。

如果對原來的節點直接修改的話,會造成不可名狀的執行錯誤(有興趣的同學可以結合上面插入**想一想為什麼),

空間和時間也無法接受(我們需要把後面所有樹都更改一下),但我們在做樹套樹的時候,可以做類似的操作,那麼主席樹是不是應該也套些什麼呢?

主席樹上的點,儲存的都是在一段權值區間內的資料個數,我們必須要維護資料個數才可以通過相減得到一段區間的權值線段樹。

而現在有了修改,對於這個修改的維護,樸素的做法有2種:o(1)查詢,o(n)維護(掃一遍),和o(n)查詢(現場算)和o(1)維護。

這兩種做法都不是很憂,所以我們考慮利用快捷維護字首和的樹狀陣列解決這個問題,即所謂「樹狀陣列套主席樹」

#include #define n 100005

#define m 40000005

using namespace std;

inline int read()

while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();

return x*f;

}inline void write(register int x)

int a[n];

int n,m;

int root[n],ls[m],rs[m],c[m];

int tot=0;

int xx[40],yy[40];

int v,d;

inline int lowbit(register int x)

inline void update(register int &now,register int l,register int r)

inline void change()

inline int query()

else

}return l;

}int main()

while(m--)

return 0;

}

主席樹詳解

給定nn個數 intint範圍內 一共mm次詢問,每次都要詢問區間 l,r l,r 的第kk大的數。其中n,m,l,rn,m,l,r均不超過2 1052 105,保證詢問有答案。顯而易見,最暴力的辦法就是區間排序然後輸出排序後第kk個數。最壞情況的時間複雜度是o nmlgn o nmlgn 不超時才...

主席樹詳解注釋

hdu 2665 可作為模板使用 題意 給出乙個整數序列 有若干個問詢 每次問詢 l,r,k 表示 l r 區間內第 k 大的值是多少 分析 比較裸的主席樹題目 首先先對於每個字首按權值建出主席樹 然後問詢的時候就可以通過減法得到區間 l,r 的資訊 由於儲存的是值域資訊 查詢k大值的時候就判斷左右...

區間第K值 主席樹詳解

序 這是一篇遲到的題解,機房的小夥伴們系統地學主席樹應該是七月份的時候,然而我沒趕上趟,當時壓根沒看懂主席樹是什麼東東。昨天晚上決定重新來過,於是請教了一位大神1113 這是他的部落格,不過好像因為手機驗證的原因很久沒有更新了 他告訴我了主席樹的始末,然後我就秒懂了,原來並沒有想象中的那麼複雜,相信...