樹鏈剖分就是將樹分割成多條鏈,而後利用數據結構(線段樹、樹狀數組等)來維護這些鏈。css
首先就是一些必須知道的概念:html
好比上面這幅圖中,用黑線鏈接的結點都是重結點,其他均是輕結點,2-十一、1-11就是重鏈,其餘就是輕鏈,用紅點標記的就是該結點所在鏈的起點,也就是咱們👇提到的top結點,還有每條邊的值實際上是進行dfs時的執行序號。node
算法中定義瞭如下的數組用來存儲上邊提到的概念:c++
名稱 | 解釋 |
---|---|
siz[u] | 保存以u爲根的子樹節點個數 |
top[u] | 保存當前節點所在鏈的頂端節點 |
son[u] | 保存重兒子 |
dep[u] | 保存結點u的深度值 |
faz[u] | 保存結點u的父親節點 |
tid[u] | 保存樹中每一個節點剖分之後的新編號(DFS的執行順序) |
rnk[u] | 保存當前節點在樹中的位置 |
除此以外,還包括兩種性質:web
首先定義如下數組:算法
xxxxxxxxxx
const int MAXN = (100000 << 2) + 10;
//Heavy-light Decomposition STARTS FORM HERE
int siz[MAXN];//number of son
int top[MAXN];//top of the heavy link
int son[MAXN];//heavy son of the node
int dep[MAXN];//depth of the node
int faz[MAXN];//father of the node
int tid[MAXN];//ID -> DFSID
int rnk[MAXN];//DFSID -> ID
算法大體須要進行兩次的DFS,第一次DFS能夠獲得當前節點的父親結點(faz數組)、當前結點的深度值(dep數組)、當前結點的子結點數量(size數組)、當前結點的重結點(son數組)windows
xxxxxxxxxx
void dfs1(int u, int father, int depth) {
/*
* u: 當前結點
* father: 父親結點
* depth: 深度
*/
// 更新dep、faz、siz數組
dep[u] = depth;
faz[u] = father;
siz[u] = 1;
// 遍歷全部和當前結點鏈接的結點
for (int i = head[u]; i; i = edg[i].next) {
int v = edg[i].to;
// 若是鏈接的結點是當前結點的父親結點,則不處理
if (v != faz[u]) {
dfs1(v, u, depth + 1);
// 收斂的時候將當前結點的siz加上子結點的siz
siz[u] += siz[v];
// 若是沒有設置太重結點son或者子結點v的siz大於以前記錄的重結點son,則進行更新
if (son[u] == -1 || siz[v] > siz[son[u]]) {
son[u] = v;
}
}
}
}
第二次DFS的時候則能夠將各個重結點鏈接成重鏈,輕節點鏈接成輕鏈,而且將重鏈(其實就是一段區間)用數據結構(通常是樹狀數組或線段樹)來進行維護,而且爲每一個節點進行編號,其實就是DFS在執行時的順序(tid數組),以及當前節點所在鏈的起點(top數組),還有當前節點在樹中的位置(rank數組)。api
xxxxxxxxxx
void dfs2(int u, int t) {
/**
* u:當前結點
* t:起始的重結點
*/
top[u] = t; // 設置當前結點的起點爲t
tid[u] = cnt; // 設置當前結點的dfs執行序號
rnk[cnt] = u; // 設置dfs序號對應成當前結點
cnt++;
// 若是當前結點沒有處在重鏈上,則不處理
if (son[u] == -1) {
return;
}
// 將這條重鏈上的全部的結點都設置成起始的重結點
dfs2(son[u], t);
// 遍歷全部和當前結點鏈接的結點
for (int i = head[u]; i; i = edg[i].next) {
int v = edg[i].to;
// 若是鏈接結點不是當前結點的重子結點而且也不是u的父親結點,則將其的top設置成本身,進一步遞歸
if (v != son[u] && v != faz[u]){
dfs2(v, v);
}
}
}
而修改和查詢操做原理是相似的,以查詢操做爲例,其實就是個LCA,不過這裏使用了top來進行加速,由於top能夠直接跳轉到該重鏈的起始結點,輕鏈沒有起始結點之說,他們的top就是本身。須要注意的是,每次循環只能跳一次,而且讓結點深的那個來跳到top的位置,避免兩個一塊兒跳從而插肩而過。數組
xxxxxxxxxx
INT64 query_path(int x, int y) {
/**
* x:結點x
* y:結點y
* 查詢結點x到結點y的路徑和
*/
INT64 ans = 0;
int fx = top[x], fy = top[y];
// 直到x和y兩個結點所在鏈的起始結點相等才代表找到了LCA
while (fx != fy) {
if (dep[fx] >= dep[fy]) {
// 已經計算了從x到其鏈中起始結點的路徑和
ans += query(1, tid[fx], tid[x]);
// 將x設置成起始結點的父親結點,走輕邊,繼續循環
x = faz[fx];
} else {
ans += query(1, tid[fy], tid[y]);
y = faz[fy];
}
fx = top[x], fy = top[y];
}
// 即使找到了LCA,可是前面也只是分別計算了從一開始到最終中止的位置和路徑和
// 若是兩個結點不同,代表仍然須要計算兩個結點到LCA的路徑和
if (x != y) {
if (tid[x] < tid[y]) {
ans += query(1, tid[x], tid[y]);
} else {
ans += query(1, tid[y], tid[x]);
}
} else ans += query(1, tid[x], tid[y]);
return ans;
}
void update_path(int x, int y, int z) {
/**
* x:結點x
* y:結點y
* z:須要加上的值
* 更新結點x到結點y的值
*/
int fx = top[x], fy = top[y];
while(fx != fy) {
if (dep[fx] > dep[fy]) {
update(1, tid[fx],tid[x], z);
x = faz[fx];
} else {
update(1, tid[fy], tid[y], z);
y = faz[fy];
}
fx = top[x], fy = top[y];
}
if (x != y)
if (tid[x] < tid[y]) update(1, tid[x], tid[y], z);
else update(1, tid[y], tid[x], z);
else update(1, tid[x], tid[y], z);
}
以這道題目爲例,能夠看出算法大體有兩種操做,分別是求任意兩個節點所鏈接的路徑和、極值,又或者是以任意一個節點做爲跟節點來求與子結點的路徑和、極值,而求區間和、區間極值正是線段樹所擅長的。數據結構
首先要構建線段樹:
x
void build(int i, int l, int r) {
/**
* i:當前結點的位置,i << 1表示左結點,+1表示右結點
* l:區間左索引
* r:區間右索引
*/
tree[i].left = l;
tree[i].right = r;
// 設置樹對應結點的值
if (l == r) {
tree[i].val = val[rnk[l]];
} else {
// 將數組按照二分的形式來拆分
int mid = (l + r) >> 1;
build(i << 1, l, mid);
build((i << 1) | 1, mid + 1, r);
tree[i].val = tree[i << 1].val + tree[(i << 1) + 1].val;
}
}
void pushdown(int i) {
/**
* 更新操做
*/
int lc = i << 1;
int rc = (i << 1) + 1;
tree[lc].val += (tree[lc].right - tree[lc].left + 1) * tag[i];
tree[rc].val += (tree[rc].right - tree[rc].left + 1) * tag[i];
tag[lc] += tag[i];
tag[rc] += tag[i];
tag[i] = 0;
}
void update(int i, int x, int y, INT64 k) {
/**
* i:起始位置
* x:區間左索引
* y:區間右索引
* k:加上的值
* 更新知足區間x-y的全部值加上k
*/
int lc = i << 1, rc = (i << 1) | 1;
if (tree[i].left > y || tree[i].right < x) return;
if (x <= tree[i].left && tree[i].right <= y) {
tree[i].val += (tree[i].right - tree[i].left + 1) * k;
tag[i] += k;
} else {
if (tag[i]) pushdown(i);
update(lc, x, y, k);
update(rc, x, y, k);
tree[i].val = tree[lc].val + tree[rc].val;
}
}
INT64 query(int i, int x, int y) {
/**
* 查詢操做
*/
int lc = i << 1, rc = (i << 1) + 1;
if (x <= tree[i].left && tree[i].right <= y)
return tree[i].val;
if (tree[i].left > y || tree[i].right < x)
return 0;
if (tag[i]) pushdown(i);
return query(lc, x, y) + query(rc, x, y);
}