dfs序就是一棵樹在dfs遍歷時組成的節點序列.
它有這樣一個特點:一棵子樹的dfs序是一個區間.
下面是dfs序的基本代碼:
void dfs(int x,int pre,int d){//L,R表示一個子樹的范圍 L[x]=++tot; dep[x]=d; for(int i=0;i<e[x].size();i++){ int y=e[x][i]; if(y==pre)continue; dfs(y,x,d+1); } R[x]=tot; }
給定一顆樹, 和每個節點的權值.下面有7個經典的關於dfs序的問題:
由於X的子樹在DFS序中是連續的一段, 只需要維護一個dfs序列,用樹狀數組實現:單點修改和區間查詢.
2. 對節點X到Y的最短路上所有點權都加一個數W, 查詢某個點的權值.這個操作等價於a. 對X到根節點路徑上所有點權加W
b. 對Y到根節點路徑上所有點權加W
c. 對LCA(x, y)到根節點路徑上所有點權值減W
d. 對LCA(x,y)的父節點 fa(LCA(x, y))到根節點路徑上所有權值減W
於是要進行四次這樣從一個點到根節點的區間修改.將問題進一步簡化, 進行一個點X到根節點的區間修改, 查詢其他一點Y時,只有X在Y的子樹內, X對Y的值才有貢獻且貢獻值為W.當單點更新X時,X實現了對X到根的路徑上所有點貢獻了W.於是只需要更新四個點(單點更新) ,查詢一個點的子樹內所有點權的和(區間求和)即可.
3. 對節點X到Y的最短路上所有點權都加一個數W, 查詢某個點子樹的權值之和.
同問題2中的修改方法, 轉化為修改某點到根節點的權值加/減W
當修改某個節點A, 查詢另一節點B時
只有A在B的子樹內, Y的值會增加
W * (dep[A] - dep[B] + 1) => W * (dep [A] + 1) - W * dep[B]
那么我們處理兩個數組就可以實現:
處理出數組Sum1,每次更新W*(dep[A]+1),和數組Sum2,每次更新W.
每次查詢結果為Sum1(R[B]) – Sum1(L[B]-1) - (Sum2(R[B]) – Sum2(L[B]-1)) * dep [B].
4. 對某個點X權值加上一個數W, 查詢X到Y路徑上所有點權之和.
求X到Y路徑上所有的點權之和, 和前面X到Y路徑上所有點權加一個數相似
這個問題轉化為
X到根節點的和 + Y到根節點的和 - LCA(x, y)到根節點的和 - fa(LCA(x,y)) 到根節點的和
更新某個點x的權值時,只會對它的子樹產生影響,對x的子樹的每個點到根的距離都加了W.
那么我們用”刷漆”(差分前綴和),更新一個子樹的權值.給L[x]加上W,給R[x]+1減去W,那么sum(1~L[k])就是k到根的路徑點權和.
5. 對節點X的子樹所有節點加上一個值W, 查詢X到Y的路徑上所有點的權值和
同問題4把路徑上求和轉化為四個點到根節點的和
X到根節點的和 + Y到根節點的和 - LCA(x, y)到根節點的和 - parent(LCA(x,y)) 到根節點的
再用刷漆只更新子樹.
修改一點A, 查詢某點B到根節點時, 只有B在A的子樹內, A對B才有貢獻.
貢獻為W * (dep[B] - dep[A] + 1) => W * (1 - dep[A]) + W * dep[B]
和第三題一樣, 用兩個sum1,sum2維護 W *(dep[A] + 1),和W.
最后答案就是sum2*dep[B]-sum1.
6. 對子樹X里所有節點加上一個值W, 查詢某個點的值.
對DFS序來說, 子樹內所有節點加W, 就是一段區間加W.
所以這個問題就是 區間修改, 單點查詢.樹狀數組+刷漆.
7.對子樹X里所有節點加上一個值W, 查詢某個子樹的權值和.
子樹所有節點加W, 就是某段區間加W, 查詢某個子樹的權值和, 就是查詢某段區間的和
區間修改區間求和,用線段樹可以很好解決.