樹鏈剖分(轉)

不得不說這個講的太清楚了,我仍是轉載一發吧。html

原文地址:http://blog.sina.com.cn/starszys算法

「在一棵樹上進行路徑的修改、求極值、求和」乍一看只要線段樹就能輕鬆解決,實際上,僅憑線段樹是不能搞定它的。咱們須要用到一種貌似高級的複雜算法——樹鏈剖分。

    樹鏈,就是樹上的路徑。剖分,就是把路徑分類爲重鏈和輕鏈。
    記siz[v]表示以v爲根的子樹的節點數,dep[v]表示v的深度(根深度爲1),top[v]表示v所在的鏈的頂端節點,fa[v]表示v的父親,son[v]表示與v在同一重鏈上的v的兒子節點(姑且稱爲重兒子),w[v]表示v與其父親節點的連邊(姑且稱爲v的父邊)在線段樹中的位置。只要把這些東西求出來,就能用logn的時間完成原問題中的操做。

    重兒子:siz[u]爲v的子節點中siz值最大的,那麼u就是v的重兒子。
    輕兒子:v的其它子節點。
    重邊:點v與其重兒子的連邊。
    輕邊:點v與其輕兒子的連邊。
    重鏈:由重邊連成的路徑。
    輕鏈:輕邊。

    剖分後的樹有以下性質:
    性質1:若是(v,u)爲輕邊,則siz[u] * 2 < siz[v];
    性質2:從根到某一點的路徑上輕鏈、重鏈的個數都不大於logn。
   

    算法實現:
    咱們能夠用兩個dfs來求出fa、dep、siz、son、top、w。
    dfs_1:把fa、dep、siz、son求出來,比較簡單,略過。
    dfs_2:⒈對於v,當son[v]存在(即v不是葉子節點)時,顯然有top[son[v]] = top[v]。線段樹中,v的重邊應當在v的父邊的後面,記w[son[v]] = totw+1,totw表示最後加入的一條邊在線段樹中的位置。此時,爲了使一條重鏈各邊在線段樹中連續分佈,應當進行dfs_2(son[v]);
           ⒉對於v的各個輕兒子u,顯然有top[u] = u,而且w[u] = totw+1,進行dfs_2過程。
           這就求出了top和w。
    將樹中各邊的權值在線段樹中更新,建鏈和建線段樹的過程就完成了。

    修改操做:例如將u到v的路徑上每條邊的權值都加上某值x。
    通常人須要先求LCA,而後慢慢修改u、v到公共祖先的邊。而高手就不須要了。
    記f1 = top[u],f2 = top[v]。
    當f1 <> f2時:不妨設dep[f1] >= dep[f2],那麼就更新u到f1的父邊的權值(logn),並使u = fa[f1]。
    當f1 = f2時:u與v在同一條重鏈上,若u與v不是同一點,就更新u到v路徑上的邊的權值(logn),不然修改完成;
    重複上述過程,直到修改完成。

    求和、求極值操做:相似修改操做,可是不更新邊權,而是對其求和、求極值。
    就這樣,原問題就解決了。鑑於鄙人語言表達能力有限,咱畫圖來看看:htm

    如右圖所示,較粗的爲重邊,較細的爲輕邊。節點編號旁邊有個紅色點的代表該節點是其所在鏈的頂端節點。邊旁的藍色數字表示該邊在線段樹中的位置。圖中1-4-9-13-14爲一條重鏈。

    當要修改11到10的路徑時。
    第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此時dep[f1] < dep[f2],所以修改線段樹中的5號點,v = 4, f2 = 1;
    第二次迭代:dep[f1] > dep[f2],修改線段樹中10--11號點。u = 2,f1 = 2;
    第三次迭代:dep[f1] > dep[f2],修改線段樹中9號點。u = 1,f1 = 1;
    第四次迭代:f1 = f2且u = v,修改結束。blog

//-------------------------------------------------------get

例題:poj 2763 Housewife Windio

具體代碼見例題中的代碼……im

相關文章
相關標籤/搜索