樹鏈剖分演算法詳解

2022-05-19 02:28:31 字數 3502 閱讀 4697

學oi也有一段時間了,感覺該搞點東西了。

於是學習了樹(熟)鏈(練)剖(pou)分(糞)

當然,學習這個演算法是需要先學習線段樹的。不懂的還是再過一段時間吧。

如果碰到一道題,要對一顆樹的兩個點中的最短路徑、以u為根的子樹之類的東西進行修改或者查詢,那麼大概就是樹鏈剖分的題了。

為什麼是盡可能?因為在一棵樹中,怎麼搞也無法保證對於每乙個節點,他的父親編號都是它的-1,所以是盡可能。那麼怎麼盡可能呢?

有很多演算法,今天提到的就是樹鏈剖分。我們把一顆樹上的所有鏈分成輕鏈重鏈,然後就可以對於每一段連續的重鏈進行線段樹上的修改了。

而劃分輕鏈和重鏈的依據是:對於每乙個節點u,v是它的兒子,v有乙個大小,就是size,代表以v為根的子樹的大小。我們選取u最大的兒子為重(zhong)兒子,其餘兒子為輕兒子。以連向重兒子的邊為重邊,剩下的邊為輕邊。

然後所有重邊連成的鏈叫做重鏈,(並不存在輕鏈)比如下圖,紅色的鏈是重鏈(注意,對於乙個葉子節點,如果連向它的是一條輕鏈,那麼他自己就是一條重鏈)

這樣,我們把一棵樹劃分成了重鏈和輕鏈,我們能保證所有重鏈都不重不漏的包含了所有的點。

那麼這些重鏈有什麼用?在劃分重鏈的過程中用到的dfs,這個dfs能保證,對於每一條重鏈,他們的dfs序是連續的!

這樣,我們就可以用線段樹(或者其他資料結構)維護了!

現在,我們把熟練剖分化成兩個部分:

1、把樹上的所有點劃分重鏈,然後求出它們的dfs序,以這個順序扔到線段樹裡面。

所以,如何實現劃分重鏈?我們需要用兩個dfs,第乙個dfs找到所有點的重兒子,第二個dfs將所有重兒子連成重鏈。

第乙個dfs:size是以當前點為根的子樹的大小,f是當前點的父親,son是當前點的重兒子。

inline void getson(int u,int fa)

return;}

第二個dfs:

inline void getdfn(int u,int t)
然後,對於線段樹的建樹,是獨立的,我們不用考慮鏈的關係。(input是輸入檔案)

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

int mid=(l+r)>>1

; build(i

<<1

,l,mid);

build(i

<<1|1,mid+1

,r);

tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%mod;

return

;}

最後是修改,查詢和修改很像,一起說了。

我們要把u到v路徑上所有的點都+k,那麼我們就把u,v中深的那個,它到它所在重邊的頂端+k。

然後跳過一條輕邊,重複上面的步驟,知道u,v到一條重邊上。

最後把u到v,+k就可以了。

inline void treeadd(int x,int y,int z)

if(depth[x]//

現在x、y都到了一條重鏈上了,然後要保證x在下面。

add(1,dfn[y],dfn[x],z);//

再只用更新他們所在的鏈就可以了。

return;}

inline

int treesum(int x,int y)

if(depth[x]

return (ans+query(1,dfn[y],dfn[x]))%mod;

}

對於線段樹上的維護,和樸素的線段樹一樣,就不多說了。

具體看ac**:(洛谷模板題)

#include #include 

#include

#include

#include

#define in(a) a=read()

#define rep(i,k,n) for(int i=k;i<=n;i++)

#define maxn 100010

using

namespace

std;

inline

intread()

intn,m,r,mod,input[maxn];

int total,head[maxn],to[maxn<<1],nxt[maxn<<1

];int

size[maxn],depth[maxn],f[maxn],son[maxn];

intcnt,dfn[maxn],link[maxn],top[maxn];

struct

nodetree[maxn

<<2

];inline

void adl(int a,int

b)inline

void getson(int u,int fa)

return;}

inline

void getdfn(int u,int t)

inline

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

int mid=(l+r)>>1

; build(i

<<1

,l,mid);

build(i

<<1|1,mid+1

,r);

tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%mod;

return;}

inline

void pushdown(int i)

inline

void add(int i,int l,int r,int k)

pushdown(i);

if(tree[i<<1].r>=l) add(i<<1

,l,r,k);

if(tree[i<<1|1].l<=r) add(i<<1|1

,l,r,k);

tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%mod;

return;}

inline

int query(int i,int l,int r)

inline

void treeadd(int x,int y,int z)

if(depth[x]//

現在x、y都到了一條重鏈上了,然後要保證x在下面。

add(1,dfn[y],dfn[x],z);//

再只用更新他們所在的鏈就可以了。

return;}

inline

int treesum(int x,int y)

if(depth[x]

return (ans+query(1,dfn[y],dfn[x]))%mod;

}int

main()

return0;

}

演算法詳解 樹鏈剖分

1 給你乙個序列,再給你一堆詢問區間,對於每個詢問區間,請你求區間內的最大值 累加和等等。對於這個問題,我們是早就做爛的了,線段樹 樹狀陣列等資料結構都能輕鬆求,這裡不再詳述。2 給你一棵樹,再給你一堆詢問,每次給你兩個點,讓你求兩個點之間的路徑中的點權最大值 點權和等等。對於這個問題,我們很顯然不...

樹鏈剖分詳解

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

詳解樹鏈剖分

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