「在一棵樹上進行路徑的修改、求極值、求和」乍一看只要線段樹就能輕鬆解決,實際上,僅憑線段樹是不能搞定它的。我們需要用到一種貌似高階的複雜演算法——樹鏈剖分。
樹鏈,就是樹上的路徑。剖分,就是把路徑分類為重鏈和輕鏈。
重兒子: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求出來,比較簡單,略過。
⒉對於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),否則修改完成;
重複上述過程,直到修改完成。
求和、求極值操作:類似修改操作,但是不更新邊權,而是對其求和、求極值。
就這樣,原問題就解決了。鑑於鄙人語言表達能力有限,咱畫圖來看看:
當要修改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,修改結束。
**資料規模大時,遞迴可能會爆棧,而非遞迴dfs會很麻煩,所以可將兩個dfs改為寬搜+迴圈。即先寬搜求出fa、dep,然後逆序迴圈求出siz、son,再順序迴圈求出top和w。
題目:spoj375、usaco december contest gold divison, "grassplant"。
**spoj375據說不「縮行」情況下最短的程式是140+行,我的是128行。
附spoj375程式(c++):
#include
#include
#include
#include
using namespace std;
const int maxn = 10010;
struct tedge
e[maxn * 2];
int tree[maxn];
int zzz, n, z, edge, root, a, b, c;
int d[maxn][3];
int first[maxn], dep[maxn], w[maxn], fa[maxn], top[maxn], son[maxn], siz[maxn];
char ch[10];
void insert(int a, int b, int c)
void dfs(int v)
}void build_tree(int v, int tp)
void update(int root, int lo, int hi, int loc, int x)
int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
update(ls, lo, mid, loc, x);
update(rs, mid+1, hi, loc, x);
tree[root] = max(tree[ls], tree[rs]);
}int maxi(int root, int lo, int hi, int l, int r)
inline int find(int va, int vb)
tmp = max(tmp, maxi(1, 1, z, w[f1], w[va]));
va = fa[f1]; f1 = top[va];
} if (va == vb) return tmp;
if (dep[va] > dep[vb]) swap(va, vb);
return max(tmp, maxi(1, 1, z, w[son[va]], w[vb])); //
}void init()
dfs(root);
build_tree(root, root);
// for (int i = 1; i < n; i++) }
inline void read()
void work()
}int main()
return 0;
}
樹鏈剖分 樹鏈剖分講解
好了,這樣我們就成功解決了對樹上修改查詢邊權或點的問題。下面放上 vector v maxn int size maxn dep maxn val maxn id maxn hson maxn top maxn fa maxn 定義 int edge 1,num 1 struct tree e ma...
樹鏈剖分講解
題目 aragorn s story 題意 給一棵樹,每個結點都有初始的權值,有m個操作,分兩種 一是從x 結點到y 結點路上所有的結點權值 z或 z,二是問x結點的權值。思路 樹鏈剖分。這是我學樹剖的第一題,建議還沒接觸過的夥伴,第一次學習的時候不要一直糾結理論,直接找一道模板題,然後找一篇ac ...
數鏈剖分(Housewife Wind )
題目大意 給你n,q,s。n指的是有n個點,q代表有q次詢問,s代表的是起點。然後接下來會有n 1條邊,雙向邊,帶有權值,對於q次詢問,如果輸入的第乙個數是1,然後接下來會輸入兩個數,t1,t2。t帶邊將第t1條邊的權值改成t2.如果第乙個數是0,接下來會輸入乙個t,詢問從s到t的花費。具體思路 對...