學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)第二個dfs:return;}
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...