樹鏈剖分 學習筆記

2022-03-26 10:28:08 字數 3693 閱讀 4232

前置知識:$dfs$序,線段樹

我們可以回顧兩個問題:

1.樹上從$s$到$t$的路徑,每個點權值加上$z$。

很簡單。遍歷整棵樹即可。

2.求樹上$s$到$t$的權值和。

$lca$可做。可以利用$lca$的性質$dis[s]+dis[t]-2*dis[lca]$做即可。時間複雜度$o(n\log n)$。

但是把這兩個問題結合起來呢?

每次更改權值後都要重新算一遍$dis$。那麼時間複雜度變成$n^2$的了。這時候,我們就需要樹鏈剖分來解決此類問題。

樹鏈剖分:把一棵樹劃分成若干鏈,轉化成若干序列,並用資料結構維護的演算法。

概念:

重兒子:父親節點的所有兒子中子樹結點數目最多($size$最大)的結點;

輕兒子:父親節點中除了重兒子以外的兒子;

重邊:父親結點和重兒子連成的邊;

輕邊:父親節點和輕兒子連成的邊;

重鏈:由多條重邊連線而成的路徑;

輕鏈:由多條輕邊連線而成的路徑;

變數宣告:

int size[maxn],son[maxn],dep[maxn],fa[maxn];//

大小、重兒子、深度、父親節點

int top[maxn],dfn[maxn],cnt;//

所在重鏈的頂點、時間戳

由上面的定義可知,重鏈一定是由輕邊連線的。下面我們對樹鏈剖分的複雜度進行證明。

因為每次跳樹跳的是輕邊,所以每跳一次後,所在樹的子樹的大小一定至少是原來的二倍。所以單次跳樹的複雜度是$o(n\log n)$。總複雜度$o(n\log^2 n)$。

兩次$dfs$

第一次$dfs$是進行$dfs$序,把每個結點的子樹大小,父親節點,重兒子和深度處理出來。

inline void dfs_son(int now,int f,int

deep)

}

第二次$dfs$是記錄時間戳,按照優先遍歷重兒子的原則,把樹處理成若干鏈。

inline void dfs(int now,int

topf)

}

然後進行線段樹建樹。**不貼了。

更新操作:

如果$s$和$t$不在乙個鏈內,那麼我們要進行跳樹操作。先把當前鏈的頂端到此結點的路徑權值處理了,然後跳到該鏈頂點的父親節點。類似於$lca$的操作。

inline void updrange(int x,int y,int

k)

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

update(

1,dfn[x],dfn[y],k);

}

inline int qrange(int x,int

y)

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

ans+=query(1

,dfn[x],dfn[y]);

ans%=mod;

return

ans;

}

如果要更新子樹,那麼就更簡單了,只有一行**:

update(1,dfn[x],dfn[x]+size[x]-1);

至此,樹鏈剖分的所有內容已講解完畢。

練習題:

1.【模板】輕重鏈剖分

2.【zjoi2008】樹的統計

3.【noi2015】軟體包管理器

4.【haoi2015】樹上操作

5.【jloi2014】松鼠的新家

都不難,稍微變通一下就是樹鏈剖分的模板題。

模板**(練習題1):

#include#define int long long

using

namespace

std;

const

int maxn=200005

;int

n,m,r,mod;

intsize[maxn],son[maxn],fa[maxn],dep[maxn],w[maxn];

inttop[maxn],wt[maxn],dfn[maxn],cnt;

int head[maxn*2

],jishu;

struct

node

edge[maxn*2

];int lazy[maxn*4

];struct

tretree[maxn*5

];inline

intread()

while(isdigit(ch))

return x*f;

}inline

void add(int

from,int

to)inline

void dfs_son(int now,int f,int

deep)

}inline

void dfs(int now,int

topf)

}inline

void build(int index,int l,int

r)

int mid=(l+r)>>1

; build(index*2

,l,mid);

build(index*2+1,mid+1

,r);

tree[index].v=(tree[index*2].v+tree[index*2+1].v)%mod;

}void pushdown(int

index)

inline

void update(int index,int l,int r,int

k)

else

}inline

int query(int index,int l,intr)}

inline

void updrange(int x,int y,int

k)

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

update(

1,dfn[x],dfn[y],k);

}inline

int qrange(int x,int

y)

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

ans+=query(1

,dfn[x],dfn[y]);

ans%=mod;

return

ans;

}inline

void updson(int x,int

k)inline

int qson(int

x)signed main()

dfs_son(r,

0,1);

dfs(r,r);

build(

1,1,n);

for (int i=1;i<=m;i++)

if (flag==2

)

if (flag==3

)

if (flag==4

)

}return0;

}

樹鏈剖分學習筆記

寫 又犯了很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 來記錄它,父親節點到...