樹鏈剖分是一種將樹轉化為一條鏈的演算法,通常和線段樹,樹狀陣列,dp等針對鏈的演算法結合使用。
正如題目所說,本文只講輕重鏈剖分
預處理首先扔出乙個定理:
樹中任意一條路徑均可以拆分成一條鏈上 \(o(log\ n)\) 個連續區間。
證明+構造:乙個dfs序就行了
但是實際**實現中,一般都是優先遍歷重兒子,這樣遍歷過後,可以保證重鏈的編 號是連續的。
這個性質尤其有用。
有了這種dfs序,我們可以讓上面的那個定理更確切一點:
樹中的每一條路徑,都可以拆分為 \(o(log\ n )\) 條重鏈(重鏈可能不完整),即可拆分為 \(o(log\ n)\) 個連續區間
所以首先我們要做的是兩個dfs對樹進行預處理
第一次dfs:找到重兒子
第二次dfs:建立dfs序,根據第一次找到的重兒子找重鏈
void dfs1(int x,int f) //尋找重兒子
ll query_tree(int x)
那路徑呢?
考慮一下lca。
遵循從特殊到一般的原則,先想想路徑兩端點若是在同乙個重鏈中會怎樣。
由於我們在搜尋時令重鏈編號連續,所以它們是乙個連續區間,直接線段樹就好了。
如果在不同的重鏈中呢?
我們就要去看深度最深的那個節點的重鏈起點,顯然它的重鏈起點和另外乙個節點一開始也不應該是相等的。於是我們繼續看它的重鏈起點的重鏈起點......直到它們重鏈起點相同,也就是在同一重鏈時為止。
這是乙個類似於倍增lca往上跳的過程,但這裡每次跳躍到的是當前重鏈的起點。每跳一次做一次線段樹。
完成這個過程的函式如下:
void update_path(int x,int y,int k)
int n,m;
int poi[n];
int id[n],nwpoi[n],cnt=0;
int dpt[n],size_[n],top[n]; //每個點的深度,每個點為根的子樹大小,所在重鏈的頂點
int fa[n],son[n];//父節點,重兒子
ll tree[n<<2],lazy[n<<2];//線段樹相關
void dfs1(int x,int f) //尋找重兒子
void build(int node,int start,int end)
int mid=start+end>>1;
int lnode=node<<1;
int rnode=node<<1|1;
build(lnode,start,mid);
build(rnode,mid+1,end);
push_up(node);
}void update(int node,int start,int end,int l,int r,int val)
push_down(node,start,end);
int mid=start+end>>1;
int lnode=node<<1;
int rnode=node<<1|1;
if(l<=mid) update(lnode,start,mid,l,r,val);
if(r>mid) update(rnode,mid+1,end,l,r,val);
push_up(node);
}ll query(int node,int start,int end,int l,int r)
void update_path(int x,int y,int k)
{ while(top[x]!=top[y]) //如果不在同一重鏈
{ if(dpt[top[x]]樹鏈剖分是一種優秀的演算法。完美地體現了化繁為簡的思想,為許多樹上操作提供了便利。樹剖碼量很大,剛學完時調**可能會很難受。但是熟練之後錯誤率就小很多了。
輕重鏈剖分
目錄樹剖完就是線段樹題了qwq 沒了題外話 鴿說叫 he y light decomposition 或 he y path decomposition 正確叫法 不是 這是真的 乙個節點子樹大小最大的兒子叫重兒子 節點到重兒子的邊叫重邊 一堆重邊叫重鏈 重兒子優先 dfs,於是重鏈連續,每條鏈可以...
模板 輕重鏈剖分
目錄後記 模板 輕重鏈剖分 傳送門總的來說,就是乙個不難理解,碼量 的東西 推幾篇題解,講得不錯 線段樹 必備 倍增lca 可以幫助理解,不會應該也可以 鏈式前向星 存圖,不會有人不會吧 重兒子 子樹結點最多的兒子 重邊 某個點到它的重兒子連成的邊 重鏈 重邊連成的鏈 輕兒子 除重兒子外的其它兒子 ...
演算法入門 樹鏈剖分 輕重鏈剖分
目錄 3.0 求 lca 4.0 利用資料結構維護資訊 5.0 例題 參考資料 資料結構入門 線段樹 發表於 2019 11 28 20 39 dfkuaid 摘要 線段樹的基本 建樹 區間查詢 單點修改 及高階操作 區間修改 單點查詢 區間修改 區間查詢 標記下傳 標記永久化 閱讀全文 樹鏈剖分用...