樹鏈剖分,說白了就是一種讓你**不得不強行增加1k的資料結構-dms個人理解:+1
:joy:
證明出題人非常毒瘤
可以非常友(bao)好(li)的解決一些樹上問題:grimacing:
樹鏈剖分的思想比較神奇
它的思想是:把一棵樹拆成若干個不相交的鏈,然後用一些資料結構去維護這些鏈
那麼問題來了
首先明確一些定義
重兒子:該節點的子樹中,節點個數最多的子樹的根節點(也就是和該節點相連的點),即為該節點的重兒子
重邊:連線該節點與它的重兒子的邊
重鏈:由一系列重邊相連得到的鏈
輕鏈:由一系列非重邊相連得到的鏈
這樣就不難得到拆樹的方法
對於每乙個節點,找出它的重兒子,那麼這棵樹就自然而然的被拆成了許多重鏈與許多輕鏈
首先,要對這些鏈進行維護,就要確保每個鏈上的節點都是連續的,
因此我們需要對整棵樹進行重新編號,然後利用dfs序的思想,用線段樹或樹狀陣列等進行維護(具體用什麼需要看題目要求,因為線段樹的功能比樹狀陣列強大,所以在這裡我就不提供樹狀陣列的寫法了)
注意在進行重新編號的時候先訪問重鏈
這樣可以保證重鏈內的節點編號連續
上面說的太抽象了,結合一張圖來理解一下
對於一棵最基本的樹
給他標記重兒子,
藍色為重兒子,紅色為重邊
然後對樹進行重新編號
橙色表示的是該節點重新編號後的序號
不難看出重鏈內的節點編號是連續的
像什麼區間加區間求和什麼的
接下來結合一道例題,加深一下對於**的理解
題目鏈結
樹鏈剖分的裸題
首先來一坨定義
int deep[maxn];//按照我們上面說的,我們首先要對整棵樹dfs一遍,找出每個節點的重兒子節點的深度
int fa[maxn];//
節點的父親
int son[maxn];//
節點的重兒子
int tot[maxn];//
節點子樹的大小
順便處理出每個節點的深度,以及他們的父親節點
int dfs1(int now, int f, int然後我們需要對整棵樹進行重新編號dep)
return
tot[now];
}
我把一開始的每個節點的權值存在了$b$陣列內
void dfs2(int now, int$idx$表示重新編號後該節點的編號是多少topf)
另外,這裡引入了乙個$top$陣列,
$top[i]$表示$i$號節點所在重鏈的頭節點(最頂上的節點)
至於這個陣列有啥用,後面再說
我們需要根據重新編完號的樹,把這棵樹的上每個點對映到線段樹上,
structtree t[maxn];
void build(int k, int ll, int另外線段樹的基本操作,rr)
int mid = (ll + rr) >> 1
; build(ls, ll, mid);
build(rs, mid + 1
, rr);
update(k);
}
這裡就不詳細解釋了
直接放**
void update(int k)
void intervaladd(int k, int ll, int rr, int val)我們考慮如何實現對於樹上的操作pushdown(k);
int mid = (t[k].l + t[k].r) >> 1
;
if (ll <=mid) intervaladd(ls, ll, rr, val);
if (rr >mid) intervaladd(rs, ll, rr, val);
update(k);
}int intervalsum(int k, int ll, int rr)
void pushdown(int k)
樹鏈剖分的思想是:對於兩個不在同一重鏈內的節點,讓他們不斷地跳,使得他們處於同一重鏈上
那麼如何"跳」呢?
還記得我們在第二次$dfs$中記錄的$top$陣列麼?
結合$deep$陣列
假設兩個節點為$x$,$y$
我們每次讓$deep[top[x]]$與$deep[top[y]]$中大的(在下面的)往上跳(有點類似於樹上倍增)
void treesum(int x, int y)在樹上查詢的這一步可能有些抽象,我們結合乙個例子來理解一下if (deep[x] >deep[y]) swap(x, y);
ans = (ans + intervalsum(1, idx[x], idx[y])) %mod;
printf(
"%d\n
", ans);
}void treeadd(int x, int y, int val)
if (deep[x] >deep[y]) swap(x, y);
intervaladd(
1, idx[x], idx[y], val);
}
還是上面那張圖,假設我們要查詢$3.6$這兩個節點的之間的點權合,為了方便理解我們假設每個點的點權都是$1$
剛開始時
$top[3]=2,top[6]=1$
$deep[top[3]]=2,deep[top[6]]=1$
我們會讓$3$向上跳,跳到$top[3]$的爸爸,也就是$1$號節點
這是$1$號節點和$6$號節點已經在同一條重鏈內,所以直接對線段樹進行一次查詢即可
這個就更簡單了
所以修改的時候直接這樣
intervaladd(1,idx[x],idx[x]+tot[x]-1,z%mod);(剛開始忘記寫了,這一塊是後來補上的)
如果邊$\left( u,v\right)$,為輕邊,那麼$size\left( v\right) \leq size\left( u\right) /2$。
證明:顯然:joy:,否則該邊會成為重邊
樹中任意兩個節點之間的路徑中輕邊的條數不會超過$\log _n$,重路徑的數目不會超過$\log _n$
證明:不會:stuck_out_tongue_winking_eye:
有了上面兩條性質,我們就可以來分析時間複雜度了
由於重路徑的數量的上界為$\log _n$,
線段樹中查詢/修改的複雜度為$\log _n$
那麼總的複雜度就是$\left( \log _n\right) ^$
樹剖可以求lca,沒想到吧
這份**是以前寫的,可能比較醜,下面兩份是剛剛寫的
有點意思
樹鏈剖分詳解
樹鏈剖分定義 只是把一棵樹拆成鏈來處理而已,即將樹上的某些段一起通過資料結構優化進行處理來降低複雜度。樹鏈剖分相關定義 重兒子 ve v 為 u 的子節點中ve 值最大的,那麼 v 就是 u的重兒子 將子樹中最長的那一條鏈一起處理來降低複雜度 輕兒子 u 除了重兒子的其它子節點。重邊 點 u與其重兒...
詳解樹鏈剖分
樹鏈剖分,顧名思義為將鏈剖開分成多條。當我們想要修改樹上一條路的值或求值時,我們暴力只能用乙個個修改,這是非常慢的。這時,我們就要想乙個辦法,資料結構?但是資料結構我們都需要連續修改,可是樹上路徑的編號是不連續的。於是我們想了乙個辦法。我們先定義 fa x 為x的父親 dep x 為x的深度 siz...
樹鏈剖分詳解
重兒子 對於每乙個非葉子節點,它的兒子中 以那個兒子為根的子樹節點數最大的兒子 為該節點的重兒子 ps 感謝 shzr大佬指出我此句話的表達不嚴謹qwq,已修改 輕兒子 對於每乙個非葉子節點,它的兒子中 非重兒子 的剩下所有兒子即為輕兒子 葉子節點沒有重兒子也沒有輕兒子 因為它沒有兒子。重邊 乙個父...