演算法入門 樹鏈剖分 輕重鏈剖分

2022-07-01 09:09:11 字數 4485 閱讀 7171

目錄#3.0 求 lca

#4.0 利用資料結構維護資訊

#5.0 例題

參考資料

[資料結構入門]線段樹

發表於 2019-11-28 20:39 dfkuaid

摘要: 線段樹的基本(建樹、區間查詢、單點修改)及高階操作(區間修改 單點查詢、區間修改 區間查詢(標記下傳、標記永久化)) 

閱讀全文   

>>   

樹鏈剖分用於將樹分割成若干條鏈的形式,以維護樹上路徑的資訊。

具體來說,將整棵樹剖分為若干條鏈,使它組合成線性結構,然後用其他的資料結構維護資訊。

重鏈剖分可以將樹上的任意一條路徑劃分成不超過 \(\log n\) 條連續的鏈,每條鏈上的點深度互不相同(即是自底向上的一條鏈,鏈上所有點的 lca 為鏈的乙個端點)。

重鏈剖分還能保證劃分出的每條鏈上的節點 dfs 序連續,因此可以方便地用一些維護序列的資料結構(如線段樹)來維護樹上路徑的資訊。

如:除了配合資料結構來維護樹上路徑資訊,樹剖還可以用來 (且常數較小)地求 lca。在某些題目中,還可以利用其性質來靈活地運用樹剖。

正如文題,本文主要講述重鏈剖分的實現

重兒子:所有兒子中子樹結點數最多的兒子

重邊:由結點與該點重兒子組成的樹邊

重鏈:由若干條重邊首尾相連組成的長鏈

我們這裡採用鄰接表儲存雙向邊(每一條樹邊存兩個方向)

這是樹剖的核心操作一

這一部分,我們主要是要完成以下任務:

很顯然,以上三個任務可以通過一次 dfs 解決

void dfs1(int x,int fa,int depth)

}

這次,我們要完成的任務有

顯然,依舊可以採用 dfs 實現,這裡有幾個值得強調的點:

void dfs2(int u,int t)

}

q:為啥優先遞迴重兒子?

別著急,我們先來看看如果這樣做會有怎樣的效果

顯然,這樣做一條重鏈上的所有結點對應的編號連續(dfs 序連續),這樣的話,如果我們要按照 dfs 序建立一顆線段樹,那麼一條重鏈對應的結點編號顯然是連續的,我們如果要對一條重鏈進行修改,可以直接修改乙個區間

好的,經過以上兩場手術,一顆完整的樹已經被解剖成為大大小小的鏈了,在進行更深一步的探索前,我們先來學習乙個必備的操作——用重鏈求樹上兩點的 lca

首先,我們考慮什麼情況可以直接返回 \(x,y\) 的 lca?

顯然是二者在同一條重鏈上時,那麼,為了達到這個目標,我們只需要每次讓所在重鏈的頭深度較大的結點向上跳,跳到該重鏈的頭的父節點,重複這個過程即可

inline int lca(int a,int b)

if (d[a] < d[b])

swap(a,b);

return b;

}

這裡以線段樹為例(因為我不會其他的)

假如我們要維護這樣一棵樹:

讓我們一樣一樣來

首先,從 \(a\) 到 \(b\) 的路徑是什麼?

是不是就是從 \(a\) 到 \(\text(a,b)\),再從 \(\text(a,b)\) 到 \(b\),那麼,我們可不可以借用上面求 lca 的思想,每跳一次,給重鏈進行區間修改,最終達到修改整個路徑

還記得我們解剖時的乙個操作嗎?讓一條重鏈上的結點對應的線段樹上的編號連續,所以就可以直接進行區間修改

inline void update(int l,int r,int x,int k)

pushdown(k);

int mid = (a[k].l + a[k].r) >> 1;

if (mid >= l)

update(l,r,x,a[k].ls);

if (mid < r)

update(l,r,x,a[k].rs);

pushup(k);

}inline void updates(int x,int y,int c)

if (id[x] > id[y])

swap(x,y);

update(id[x],id[y],c,rt);

}

首先,dfs 序具有這樣乙個性質:一顆子樹上的點 dfs 序連續

所以我們就可以直接採用如下**進行區間修改

update(id[x],id[x] + size[x] - 1,y,rt);
與路徑修改一樣的思想

inline int query(int l,int r,int k)

inline int sum(int x,int y)

if (id[x] > id[y])

swap(x,y);

return (ans + query(id[x],id[y],rt)) % mod;

}

與子樹修改類似

洛谷 p3384 【模板】輕重鏈剖分

板子題

#include #include #include #include #include #define inf 0x3fffffff

#define n 100100

#define ll long long

#define mset(l,x) memset(l,x,sizeof(l))

#define mp(a,b) make_pair(a,b)

using namespace std;

struct edge;

edge e[n * 4];

struct node;

node a[n * 4];

int n,m,root,mod;

int value[n],head[n];

int d[n],f[n],size[n],son[n],tot;

int top[n],id[n],rk[n],rt;

inline void add_edge(int u,int v)

void dfs1(int x,int fa,int depth)

}void dfs2(int u,int t)

}inline int len(int k)

inline void pushup(int k)

inline void add(int k,int x)

inline void build(int l,int r,int k)

int mid = (l + r) >> 1;

a[k].ls = tot ++;

build(l,mid,a[k].ls);

a[k].rs = tot ++;

build(mid + 1,r,a[k].rs);

a[k].l = a[a[k].ls].l,a[k].r = a[a[k].rs].r;

pushup(k);

}inline void pushdown(int k)

}inline void update(int l,int r,int x,int k)

pushdown(k);

int mid = (a[k].l + a[k].r) >> 1;

if (mid >= l)

update(l,r,x,a[k].ls);

if (mid < r)

update(l,r,x,a[k].rs);

pushup(k);

}inline int query(int l,int r,int k)

inline int sum(int x,int y)

if (id[x] > id[y])

swap(x,y);

return (ans + query(id[x],id[y],rt)) % mod;

}inline void updates(int x,int y,int c)

if (id[x] > id[y])

swap(x,y);

update(id[x],id[y],c,rt);

}int main()

dfs1(root,0,1);

tot = 1;

dfs2(root,root);

tot = 0;

build(1,n,rt = tot ++);

while (m --)

else if (s == 2)

else if (s == 3)

else

}return 0;

}

oi-wiki - 樹鏈剖分

輕重鏈剖分

樹鏈剖分是一種將樹轉化為一條鏈的演算法,通常和線段樹,樹狀陣列,dp等針對鏈的演算法結合使用。正如題目所說,本文只講輕重鏈剖分 預處理首先扔出乙個定理 樹中任意一條路徑均可以拆分成一條鏈上 o log n 個連續區間。證明 構造 乙個dfs序就行了 但是實際 實現中,一般都是優先遍歷重兒子,這樣遍歷...

輕重鏈剖分

目錄樹剖完就是線段樹題了qwq 沒了題外話 鴿說叫 he y light decomposition 或 he y path decomposition 正確叫法 不是 這是真的 乙個節點子樹大小最大的兒子叫重兒子 節點到重兒子的邊叫重邊 一堆重邊叫重鏈 重兒子優先 dfs,於是重鏈連續,每條鏈可以...

模板 輕重鏈剖分

目錄後記 模板 輕重鏈剖分 傳送門總的來說,就是乙個不難理解,碼量 的東西 推幾篇題解,講得不錯 線段樹 必備 倍增lca 可以幫助理解,不會應該也可以 鏈式前向星 存圖,不會有人不會吧 重兒子 子樹結點最多的兒子 重邊 某個點到它的重兒子連成的邊 重鏈 重邊連成的鏈 輕兒子 除重兒子外的其它兒子 ...