最近正在學習一種資料結構——可持久化線段樹。看了網上的許多部落格,弄了幾道模板題,思路有點亂了,所以還是來總結整理下吧。
你需要維護這樣的乙個長度為\(n\)的陣列,支援如下幾種操作
在某個歷史版本上修改某乙個位置上的值
訪問某個歷史版本上的某一位置的值
此外,每進行一次操作(對於操作2,即為生成乙個完全一樣的版本,不作任何改動),就會生成乙個新的版本。版本編號即為當前操作的編號(從1開始編號,版本0表示初始狀態陣列)
輸入的第一行包含兩個正整數\(n,m\)分別表示陣列的長度和操作的個數。
第二行包含 n n個整數,依次為初始狀態下陣列各位的值(依次為\(a_i, 1 \leq i \leq n\))。
接下來\(m\)行每行包含3或4個整數,代表兩種操作之一(\(i\)為基於的歷史版本號):
對於操作1,格式為\(v_i \ 1 \ _i \ _i v\),即為在版本\(v_i\)的基礎上,將\(a__i}\)修改為\(_i\)
對於操作2,格式為\(v_i \ 2 \ _i\),即訪問版本\(v_i\)中的\(a__i}\)的值
輸出包含若干行,依次為每個操作2的結果。
5 10
59 46 14 87 41
0 2 1
0 1 1 14
0 1 1 57
0 1 1 88
4 2 4
0 2 5
0 2 4
4 2 1
2 2 2
1 1 5 91
5987
4187
8846
對於30%的資料:\(1 \leq n, m \leq ^3\)
對於50%的資料:\(1 \leq n, m \leq ^4\)
對於70%的資料:\(1 \leq n, m \leq ^5\)
對於100%的資料:\(1 \leq n, m \leq ^6, 1 \leq _i \leq n, 0 \leq v_i < i, -^9 \leq a_i, _i \leq ^9\)
經測試,正常常數的可持久化陣列可以通過,請各位放心
資料略微**,請注意常數不要過大
另,此題i/o量較大,如果實在tle請注意i/o優化
詢問生成的版本是指你訪問的那個版本的複製
一共11個版本,編號從0-10,依次為:
0 : 59 46 14 87 41
1 : 59 46 14 87 41
2 : 14 46 14 87 41
3 : 57 46 14 87 41
4 : 88 46 14 87 41
5 : 88 46 14 87 41
6 : 59 46 14 87 41
7 : 59 46 14 87 41
8 : 88 46 14 87 41
9 : 14 46 14 87 41
10 : 59 46 14 87 91
很裸的可持久化線段樹板子題。可持久嘛!就是當出現歷史版本的時候,能夠非常方便地維護乙個區間的歷史版本。
自然,我們需要建\(n\)棵線段樹。最粗暴的想法,對每個新版本都把原版本內容複製一遍,然後修改對應的值。這根本不用想,直接mle+tle。那維護歷史版本又是怎樣實現的呢?
對於本題,每個版本的序列,我們可以建一棵線段樹來維護它,所有非葉子節點表示的是一段區間,而葉子節點就表示序列的每乙個值了。
舉個栗子,樣例中初始版本可以長這樣——
而版本1只是查詢了一下(線段樹基本操作,這裡不再贅述),然後跟初始版本一模一樣。這就沒必要複製了嘛!我們設版本\(i\)有乙個根節點\(root_i\)(表示整段區間),根節點有左右兒子,那麼我們直接讓\(root_1\)的左右兒子指向\(root_0\)的左右兒子就好了,根本不用複製整個線段樹嘛!
那再來看看修改操作。比如從版本1~2。1和0是一樣的,而版本2會長這樣——
有沒有發現1和2真的很像?其實從前到後只改變了乙個節點!那麼其他相同的地方,我們可不可以共用一段記憶體呢?
沒錯,每次建立乙個新的版本時,只要新建\(\log_2 n\)個節點,也就是只儲存從新版本的根節點到更新的那乙個葉子節點的路徑就可以了,不在此路徑上的左/右兒子只要接原版本對應區間的對應兒子就可以啦。我們可以保證,從對應版本的根節點一定能訪問到對應葉子節點的值。
下面是加入新版本的具體實現**(我寫的是非遞迴版):
#define r register int
inline void insert(r*t,r u,r l,r r,r k)
//t是當前節點指標,u是原版本對應t的節點,l、r為當前區間,k為修改點的位置
in(val[*t=++p]);//讀入新葉子節點的值
}
整個程式的**如下
#include#include#define r register int
const int n=1000009,m=20000009;
int p,rt[n],lc[m],rc[m],val[m];
char i[m<<1],o[m],*fi=i,*fo=o;
bool nega;
inline void in(r&z)
void oi(r z)
inline void out(r z)
//上面快讀快寫
void build(r&t,r l,r r)//初始化建樹,線段樹基本操作
else in(val[p]);
}inline void insert(r*t,r u,r l,r r,r k)//更新,插入乙個新路徑
in(val[*t=++p]);
}inline int ask(r t,r l,r r,r k)//詢問
return val[t];
}int main()
} fwrite(o,1,fo-o,stdout);
fclose(stdin);fclose(stdout);
return 0;
}
可持久化線段樹
可持久化線段樹,意思是可以查詢歷史記錄的線段樹。又叫主席樹。我們可以通過記錄不同的根節點,並在每乙個更新到的節點處新建必要的節點。詢問不同版本的主席樹,只需要進入不同的根節點即可。例題 給定n,m,輸入n個數組成的數列,有m個詢問,每次詢問l,r這個區間中,第k小的數的值。分析 這個題可以巧妙運用主...
可持久化線段樹
以p3919 模板 可持久化陣列 可持久化線段樹 平衡樹 為例。知識點 1.練習可持久化線段樹 2.線段樹維護數列。線段樹維護數列單點查詢僅需o logn 3.記得return root 4.記得設定左右兒子 5.有時需注意cnt的初始大小 include using namespace std i...
可持久化線段樹
可持久化資料結構總是可以保留每乙個歷史版本,並且支援操作的不可變特性。對於這個說法,我表示非常贊同,因為可持久化的標誌就是在修改的過程中仍然可以保持原子樹的性質,對於直接全域性更改,倒不如把原來的一部分留下,用新的空間來記錄當前更改後的值,只改我們需要得到那一部分,因此我們開始研究可持久化的資料結構...