dfs序的常見用法整理

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序的問題:

1. 對某個節點X權值加上一個數W, 查詢某個子樹X裏所有點權的和.

由於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, 查詢某個子樹的權值和, 就是查詢某段區間的和
區間修改區間求和,用線段樹可以很好解決.

發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章