樹鏈剖分學習筆記

2022-04-28 20:51:16 字數 4848 閱讀 5203

寫在前面目錄一、簡介

二、演算法流程

三、**實現

四、經典例題

題目完成進度

一、簡介

樹鏈剖分通常用於解決一類維護靜態樹上路徑資訊的問題,例如,給定一棵點帶權的樹,接下來每次操作會修改某條路徑上所有點的權值(修改為同乙個值或是同加上乙個值等),以及詢問某條路徑上所有點的權值和。

當這棵樹是一條鏈時,這個問題實際上就是乙個序列上區間修改、區間詢問的問題,可以用線段樹等資料結構解決。

而對於其他情況,由於樹的形態是不變的,因此樹鏈剖分的策略是將這些點按照某種方式組織起來,剖分成若干條鏈,每條鏈就相當於乙個序列。這樣,操作的路徑可以拆分為某幾條鏈,也就是若干個完整序列或是某個序列上的一段區間,此時可以利用線段樹等處理序列上區間操作的資料結構解決問題。

樹鏈剖分的核心是如何恰當地將樹剖分為若干條鏈。當鍊的劃分方式確定後,我們只要將它們看做是乙個個序列,將所有序列按順序拼接起來,每條鏈就成為了一段區間,而序列上的區間問題是我們所熟悉和擅長解決的(並沒有擅長qaq)

go back

二、演算法流程

下面以點帶權的樹為例,結合路徑權值修改、路徑詢問權值和等問題,介紹常用的剖分方法——重鏈剖分

我們將樹中的邊分為兩種:輕邊和重邊。下圖中加粗的為重邊,其餘為輕邊

接下來我就詳細講講究竟如何把一棵樹剖分成鏈qwq

1.從根結點出發(如果是無根樹就任意指定一點為根),記size[u]為以結點u為跟的子樹的結點個數(包括u本身)

2.找到u的所有兒子中size值最大的乙個結點記為v,則(u,v)為重邊,v稱為u的重兒子,u到其他兒子的邊為輕邊。如果存在兒子結點的size值相等的情況,則隨便選乙個size值最大的兒子為重兒子即可

懂了嗎?

這裡補充幾個輕重邊的性質

1.如果(u,v)為輕邊,則size[v]≤size[u]/2

證明:反證法。

設size[v]>size[u]/2,則size[v]必定比其他兒子的size值要大,即v一定為u的重兒子且(u,v)為重邊,這與前提條件(u,v)為輕邊不符

2.從根到某一結點v的路徑上的輕邊個數不多於log2n(n為整棵樹的總結點個數)

證明:v為葉子結點時輕邊數量最多。

而由上乙個性質可知,每經過一條輕邊,子樹的結點個數至少比原來少一半,所以至多經過log2n條輕邊就到達葉子節點

3.我們稱某條路徑為重路徑(重鏈),當且僅當它全部由重邊組成(特殊的,乙個點也算一條重路徑)。那麼對於每個點到根結點的路徑上都有不超過log2n條輕邊和log2n條重路徑

證明:顯然每條重路徑的起點和終點都是由輕邊構成,而由上乙個性質可知,每個點到根結點的輕邊個數最多為log2n,所以重路徑數量也最多為log2n

同時我們也容易發現,乙個點在且只在一條重路徑上,而且每條重路徑一定是一條從根結點方向向葉子結點方向延伸的深度遞增的路徑(因為乙個非葉子結點有且只有乙個重兒子)

那麼具體要怎麼實現剖分的操作呢?

輕、重邊剖分的過程可以用兩次dfs實現,有時為了防止遞迴過深而導致棧溢位,也可以用bfs實現

剖分過程中要計算以下7個值

1.fa[x]:x在樹中的父親

2.dep[x]:x在樹中的深度

3.size[x]:x的子樹結點數(包括它自己)

4.son[x]:x的重兒子,即(x,son[x])為重邊

5.top[x]:x所在重路徑的頂部結點(深度最小)

7.rev[x]:線段樹中第x個位置對應的樹中結點編號,即seg[rev[x]]=x

第一遍dfs時可以計算出前4個值,第二遍dfs時可以計算出後3個值

而計算seg時,同一條重路徑上的點需要按順序排在連續的一段位置,也就是一段區間

maya這裡我不太懂,回頭再來看看吧qaq

好了現在剖分完了,我們就要開始執行操作了qwq

假設我們要處理路徑(u,v),我們可以分別處理u,v兩個點到其lca的路徑。根據性質3,路徑最多可以被分解成log2n條的輕邊和log2n條的重路徑,那麼現在我們只考慮如何維護這兩種物件,即如何維護重路徑和輕邊

1.對於重路徑,它們此時相當於乙個序列,因此我們只需要用線段樹來維護

2.對於輕邊,我們可以直接跳過,訪問下一條重路徑,因為輕邊的端點一定在某兩條重路徑上

這兩種操作的時間複雜度分別為o(log2n)和o(logn),因此總複雜度為o(log2n)

將一條路徑(u,v)拆分為若干條重路徑的過程,實際上就是乙個尋找lca的過程

在這裡我們不需要暴力的做法,因為我們已經完成了樹鏈剖分,可以直接利用樹鏈剖分求出lca。

2.當top[u]=top[v]時,說明他們走到了同一條重路徑上,這時他們之間的路徑也是序列上的一段區間,且此時u,v中深度較小的那個結點是原路徑的lca

這樣我們就可以將給出的任意路徑拆分成若干條重路徑,也就是若干個區間,並用線段樹等資料結構處理操作qwq

go back

三、**實現

吶先放乙個模板的鏈結在這裡啦^_^

1.預處理剖分

void dfs1(int u,int

f) }

return;}

void dfs2(int u,int

f)

for(e=first[u];e;e=next[e])}}

2.構建線段樹

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

build(k

<<1

,l,mid);

build((k

<<1)+1,mid+1

,r);

sum[k]=sum[k<<1]+sum[(k<<1)+1

]; max[k]=max(max[k<<1],max[(k<<1)+1

]);

return

;}

3.查詢(u,v)路徑資訊

void down(int k,int l,int

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

int mid=(l+r)>>1,res=0

;

if(mid>=l) query(k<<1

,l,mid,l,r);

if(mid1)+1,mid+1

,r,l,r);

return;}

void ask(int u,int v)

if(dep[u]>dep[v]) swap(u,v);//

此時已在同一條重鏈上,深度較小的為lca

query(1,1,seg[0

],seg[u],seg[v]);

return

;}

4.修改(u,v)路徑資訊

void change(int k,int l,int r,int l,int r,int

val)

int mid=(l+r)>>1,res=0

;

if(mid>=l) change(k<<1

,l,mid,l,r);

if(mid1)+1,mid+1

,r,l,r);

return;}

void change(u,v,val)

if(dep[u]>dep[v]) swap(u,v);

change(

1,1,seg[0

],seg[u],seg[v],val);

return

;}

5.單點修改資訊

void change_point(int k,int l,int r,int val,int

pos)

int mid=(l+r)>>1

;

if(mid>=pos) change(k<<1

,l,mid,val,pos);

if(mid+1

<=pos) change((k<<1)+1,mid+1

,r,val,pos);

sum[k]=sum[k<<1]+sum[(k<<1)+1

]; max[k]=max(max[k<<1],max[(k<<1)+1

]);}

7.查詢以u為根結點的子樹的資訊

void ask_tree(int

u)

8.修改以u為根結點的子樹的資訊

void change_tree(int

u)

9.完整樹鏈剖分模板go back

四、經典例題

咕咕咕咕

go back

blog

樹鏈剖分學習筆記

寫 又犯了很sb的錯誤,線段樹寫錯了。好像每次都會把r l 1寫成l r 1,然後就只有20分。寫的比較醜,壓了壓之後190行。基本上是我打過的最長的乙個模板了 然後簡單介紹一下樹剖吧。樹鏈剖分,就是把樹剖分成鏈,然後用資料結構來維護這些鏈,使得詢問 修改的複雜度達到o logn o l ogn 不...

樹鏈剖分學習筆記

樹鏈剖分 mod estc oder modestcoder modest code r如果你是重兒子,你就在重路徑上。如果你是輕兒子,暴力沿著祖先向上爬最多log nlogn logn 次就可以遇到重路徑。或者到根 而樹上操作基本就是找祖先 也許有人喜歡我的碼風 include include d...

樹鏈剖分學習筆記

前言 書上只講了重鏈剖分,菜雞也只會這一種,想看其他的是別想了。要會樹鏈剖分,首先你需要了解一些概念。我們把乙個節點的所有兒子節點中子樹節點數最大的稱為重兒子,也就是size最大的子節點。size的定義我在講換根dp時說過,因此不再贅述。對於每個節點的重兒子,我們用 son x 來記錄它,父親節點到...