樹鏈剖分詳解

2022-05-02 12:21:09 字數 4015 閱讀 1360

樹鏈剖分,說白了就是一種讓你**不得不強行增加1k的資料結構-dms

個人理解:+1

:joy:

證明出題人非常毒瘤

可以非常友(bao)好(li)的解決一些樹上問題:grimacing:

樹鏈剖分的思想比較神奇

它的思想是:把一棵樹拆成若干個不相交的鏈,然後用一些資料結構去維護這些鏈

那麼問題來了

首先明確一些定義

重兒子:該節點的子樹中,節點個數最多的子樹的根節點(也就是和該節點相連的點),即為該節點的重兒子

重邊:連線該節點與它的重兒子的邊

重鏈:由一系列重邊相連得到的鏈

輕鏈:由一系列非重邊相連得到的鏈

這樣就不難得到拆樹的方法

對於每乙個節點,找出它的重兒子,那麼這棵樹就自然而然的被拆成了許多重鏈與許多輕鏈

首先,要對這些鏈進行維護,就要確保每個鏈上的節點都是連續的,

因此我們需要對整棵樹進行重新編號,然後利用dfs序的思想,用線段樹或樹狀陣列等進行維護(具體用什麼需要看題目要求,因為線段樹的功能比樹狀陣列強大,所以在這裡我就不提供樹狀陣列的寫法了)

注意在進行重新編號的時候先訪問重鏈

這樣可以保證重鏈內的節點編號連續

上面說的太抽象了,結合一張圖來理解一下

對於一棵最基本的樹

給他標記重兒子,

藍色為重兒子,紅色為重邊

然後對樹進行重新編號

橙色表示的是該節點重新編號後的序號

不難看出重鏈內的節點編號是連續的

像什麼區間加區間求和什麼的

接下來結合一道例題,加深一下對於**的理解

題目鏈結

樹鏈剖分的裸題

首先來一坨定義

int deep[maxn];//

節點的深度

int fa[maxn];//

節點的父親

int son[maxn];//

節點的重兒子

int tot[maxn];//

節點子樹的大小

按照我們上面說的,我們首先要對整棵樹dfs一遍,找出每個節點的重兒子

順便處理出每個節點的深度,以及他們的父親節點

int dfs1(int now, int f, int

dep)

return

tot[now];

}

然後我們需要對整棵樹進行重新編號

我把一開始的每個節點的權值存在了$b$陣列內

void dfs2(int now, int

topf)

$idx$表示重新編號後該節點的編號是多少

另外,這裡引入了乙個$top$陣列,

$top[i]$表示$i$號節點所在重鏈的頭節點(最頂上的節點)

至於這個陣列有啥用,後面再說

我們需要根據重新編完號的樹,把這棵樹的上每個點對映到線段樹上,

struct

tree t[maxn];

void build(int k, int ll, int

rr)

int mid = (ll + rr) >> 1

; build(ls, ll, mid);

build(rs, mid + 1

, rr);

update(k);

}

另外線段樹的基本操作,

這裡就不詳細解釋了

直接放**

void update(int k) 

void intervaladd(int k, int ll, int rr, int val) 

pushdown(k);

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

;

if (ll <=mid) intervaladd(ls, ll, rr, val);

if (rr >mid) intervaladd(rs, ll, rr, val);

update(k);

}int intervalsum(int k, int ll, int rr)

void pushdown(int k)

我們考慮如何實現對於樹上的操作

樹鏈剖分的思想是:對於兩個不在同一重鏈內的節點,讓他們不斷地跳,使得他們處於同一重鏈上

那麼如何"跳」呢?

還記得我們在第二次$dfs$中記錄的$top$陣列麼?

結合$deep$陣列

假設兩個節點為$x$,$y$

我們每次讓$deep[top[x]]$與$deep[top[y]]$中大的(在下面的)往上跳(有點類似於樹上倍增)

void treesum(int x, int y) 

if (deep[x] >deep[y]) swap(x, y);

ans = (ans + intervalsum(1, idx[x], idx[y])) %mod;

printf(

"%d\n

", ans);

}void treeadd(int x, int y, int val)

if (deep[x] >deep[y]) swap(x, y);

intervaladd(

1, idx[x], idx[y], val);

}

在樹上查詢的這一步可能有些抽象,我們結合乙個例子來理解一下

還是上面那張圖,假設我們要查詢$3.6$這兩個節點的之間的點權合,為了方便理解我們假設每個點的點權都是$1$

剛開始時

$top[3]=2,top[6]=1$

$deep[top[3]]=2,deep[top[6]]=1$

我們會讓$3$向上跳,跳到$top[3]$的爸爸,也就是$1$號節點

這是$1$號節點和$6$號節點已經在同一條重鏈內,所以直接對線段樹進行一次查詢即可

這個就更簡單了

所以修改的時候直接這樣

intervaladd(1,idx[x],idx[x]+tot[x]-1,z%mod);
(剛開始忘記寫了,這一塊是後來補上的)

如果邊$\left( u,v\right)$,為輕邊,那麼$size\left( v\right) \leq size\left( u\right) /2$。

證明:顯然:joy:,否則該邊會成為重邊

樹中任意兩個節點之間的路徑中輕邊的條數不會超過$\log _n$,重路徑的數目不會超過$\log _n$

證明:不會:stuck_out_tongue_winking_eye:

有了上面兩條性質,我們就可以來分析時間複雜度了

由於重路徑的數量的上界為$\log _n$,

線段樹中查詢/修改的複雜度為$\log _n$

那麼總的複雜度就是$\left( \log _n\right) ^$

樹剖可以求lca,沒想到吧

這份**是以前寫的,可能比較醜,下面兩份是剛剛寫的

有點意思

樹鏈剖分詳解

樹鏈剖分定義 只是把一棵樹拆成鏈來處理而已,即將樹上的某些段一起通過資料結構優化進行處理來降低複雜度。樹鏈剖分相關定義 重兒子 ve v 為 u 的子節點中ve 值最大的,那麼 v 就是 u的重兒子 將子樹中最長的那一條鏈一起處理來降低複雜度 輕兒子 u 除了重兒子的其它子節點。重邊 點 u與其重兒...

詳解樹鏈剖分

樹鏈剖分,顧名思義為將鏈剖開分成多條。當我們想要修改樹上一條路的值或求值時,我們暴力只能用乙個個修改,這是非常慢的。這時,我們就要想乙個辦法,資料結構?但是資料結構我們都需要連續修改,可是樹上路徑的編號是不連續的。於是我們想了乙個辦法。我們先定義 fa x 為x的父親 dep x 為x的深度 siz...

樹鏈剖分詳解

重兒子 對於每乙個非葉子節點,它的兒子中 以那個兒子為根的子樹節點數最大的兒子 為該節點的重兒子 ps 感謝 shzr大佬指出我此句話的表達不嚴謹qwq,已修改 輕兒子 對於每乙個非葉子節點,它的兒子中 非重兒子 的剩下所有兒子即為輕兒子 葉子節點沒有重兒子也沒有輕兒子 因為它沒有兒子。重邊 乙個父...