長鏈剖分學習筆記


長鏈剖分學習筆記

簡介

長鏈剖分也是一種樹鏈剖分,平時我們說樹鏈剖分,一般都是直接默認為輕重鏈剖分。
輕重鏈剖分的優秀性質在於從任意一個點開始,向上跳躍,跳過的重鏈數量不會超過\(log\)級別。
這樣子可以很優秀的解決兩點之間鏈的問題。
對於解決一些子樹的信息問題,我們可以用\(dsu\ on\ tree\)的思路,保證了每個點向上修改的次數不超過\(log\)次,也可以很方便的解決一些問題。而長鏈剖分則是通過修改剖分鏈的方式,通過維護一些信息,可以在更有優秀的時間中解決一部分問題。
長鏈剖分十分類似於輕重鏈剖分,但是我們稍加修改,將每次選擇子樹大小最大的兒子作為重兒子變成了選擇子樹深度最大的那個兒子作為重兒子。然后將所有點和它的重兒子之間的邊認為是重邊,如果我們把他們在樹中全部加粗,那么原樹就被分割成了若干條鏈。因為很多東西都和輕重鏈剖分是相同的,所以這一部分我寫的很簡單。甚至於連兩者之間的代碼都是非常的相似的。

void dfs1(int u,int ff)
{
	md[u]=dep[u]=dep[ff]+1;fa[u]=ff;
	for(int i=h[u];i;i=e[i].next)
	{
		int v=e[i].v;if(v==ff)continue;
		dfs1(v,u);
		if(md[v]>md[hson[u]])hson[u]=v,md[u]=md[v];
	}
}
void dfs2(int u,int tp)
{
	top[u]=tp;len[u]=md[u]-dep[top[u]]+1;
	if(hson[u])dfs2(hson[u],tp);
	for(int i=h[u];i;i=e[i].next)
		if(e[i].v!=fa[u]&&e[i].v!=hson[u])
			dfs2(e[i].v,e[i].v);
}

稍微介紹一下每個數組表示的含義分別是什么。
\(dep\)是深度,\(fa\)是父親節點,\(md\)\(maxdep\)也就是子樹中的最大深度
\(hson\)是重兒子,\(top\)是這條重鏈深度最小的點,也就是重鏈的頂點,\(len\)是重鏈的長度。
代碼應該還是比較清楚地,所以就不在過多的解釋了。
如果沒有學過輕重鏈剖分,可以先去學習一下,大概直接百度樹鏈剖分就好了。

簡單的性質

性質不多,長鏈剖分的重點在於題目。但是所有題目的復雜度都和性質相關。

性質一

所有鏈長度的和是\(O(n)\)級別的。

證明:

所有點在且僅在一條重鏈之中,永遠只會被計算一次,因為鏈長的總和是\(O(n)\)級別的。

性質二

任意一個點的\(k\)次祖先\(y\)所在的長鏈的長度大於等於\(k\)

證明:
假如\(y\)所在的長鏈的長度小於\(k\),那么它所在的鏈一定不是重鏈,因為\(y-x\) 這條鏈顯然更優,那么\(y\)所在的重鏈長度至少為\(k\),性質成立。
否則\(y\)所以在長鏈長度大於等於\(k\),性質成立。

性質三

任何一個點向上跳躍重鏈的次數不會超過\(\sqrt n\)

證明:
如果一個點\(x\)從一條重鏈跳到了另外一條重鏈上,那么跳躍到的這條重鏈的長度不會小於之前的重鏈長度。
那么在最壞的情況下,重鏈長度分別為\(1,2,3,...,\sqrt n\),也就是最多跳躍\(\sqrt n\)次。
從這點上就可以看出,如果用長鏈剖分來解決兩點之間的鏈以及\(LCA\)問題,復雜度是不優於樹鏈剖分的。

一些應用

一、\(O(nlogn)-O(1)\)計算\(k\)次祖先

\(k\)次祖先我們有幾種方法,可以樹鏈剖分之后跳重鏈,這樣是\(O(n)-O(logn)\)
還可以提前預處理好倍增數組,這樣是\(O(nlogn)-O(logn)\)的。
我之前還自己\(yy\)了一種辣雞做法,先樹鏈剖分再倍增,似乎可以做到\(O(nlogn)-O(loglogn)\)
還可以對於每個點,暴力維護\([1,\sqrt n]\)次祖先,這樣子是\(O(n\sqrt n)-O(\sqrt n)\)
當然,如果支持離線的話,可以做到\(O(n+q)\),只需要\(dfs\)的時候維護一個棧就好了。
然而這些都不夠優秀,我們來考慮一下長鏈剖分的性質,看能否優化上述東西。

我們再看看\(O(n\sqrt n)-O(\sqrt n)\)的做法,它可以結合倍增,這樣的話,它的本質就是優化掉了很小的幾個二進制位。再回頭看看上面的第二條性質:\(k\)次祖先所在的重鏈長度不小於\(k\)。利用這個性質,我們就可以得到一個很秒的方法了。我們把\(k\)折半,假設是\(r\),不難發現\(r\)次祖先所在的重鏈長度不短於\(r\)。如果我們提前維護出每條重鏈從上往下的每一個點,以及對於每個重鏈的頂端,維護它的重鏈長度個祖先,這樣子對於每次找到\(r\)次祖先之后,我們就可以通過一些判斷,找到\(k\)次祖先的位置,而最后這一次尋找不難發現可以利用上面預處理出來的重鏈和祖先\(O(1)\)的計算。那么現在的復雜度瓶頸又回到了找\(r\)次祖先。我們直接找\(r\)次祖先真的優秀嗎?如果利用倍增尋找的話,仍然是\(O(logn)\)的復雜度。但是我們發現\(r\)只需要滿足\(r>k/2\)就可以向上面那么做了,因此我們令\(r\)\(k\)的最高二進制位,也就是\(r=highbit(k)\),這樣子就可以倍增預處理出來\(r\)倍祖先,然后\(O(1)\)找到\(k\)次祖先了。

補充一些復雜度以及空間的細節:根據性質一,對於每個重鏈的頂點,維護整條鏈以及它小於鏈長次祖先,因為總的點數是\(O(n)\)級別的,所以這里的時空負載度都是\(O(n)\)\(highbit\)顯然可以\(O(n)\)預處理,這樣子時空復雜度還是\(O(n)\)。倍增的時空復雜度是\(O(nlogn)\),這個方法的復雜度瓶頸在此。

綜上所述,我們得到了一個預處理\(O(nlogn)\),單次詢問\(O(1)\)的方法。

代碼&題目鏈接

二、快速計算可合並的以深度為下標的子樹信息

這個思路十分類似於\(dsu\ on\ tree\),我們長鏈剖分之后,每次不重新計算,全部繼承重兒子的值,然后再把其他的所有輕兒子的貢獻額外的算進來。

直接這樣子說十分的不清晰,我們找到題目來說。

BZOJ4543 Hotel加強版

題目&代碼鏈接

因為會在這里寫比較詳細的題解,所以上面寫得很\(Simple\)
我們先考慮一個\(O(n^2)\)\(dp\),也就是原題的做法。
我們考慮一下,三個點兩兩的距離相同是什么情況,

1.存在一個三個點公共的\(LCA\),所以我們在\(LCA\)統計答案即可。

2.存在一個點,使得這個點到另外兩個子樹中距離它為\(d\)的點以及這個點的\(d\)次祖先。

所以,設\(f[i][j]\)表示以\(i\)為根的子樹中,距離當前點為\(j\)的點數。
\(g[i][j]\)表示以\(i\)為根的子樹中,兩個點到\(LCA\)的距離為\(d\),並且他們的\(LCA\)\(i\)的距離為\(d-j\)的點對數。

考慮合並的時候的轉移:
\(ans+=g[i][0],ans+=g[i][j]*f[son][j-1],f[i][j]+=f[son][j-1],g[i][j]+=g[son][j+1]\)
轉移的正確性比較顯然,不在多講了,並不是這里的重點。
這樣子的復雜度是\(O(n^2)\)的。

我們觀察一下轉移的時候有這樣兩步:\(f[i][j]+=f[son][j-1],g[i][j]+=g[son][j+1]\)
如果我們欽定一個兒子的話,那么這個數組是可以直接賦值的,並不需要再重復計算。
所以我們用指針來寫,也就是:\(f[i]=f[son]-1,g[i]=g[son]+1\)
如果整棵樹是鏈我們發現復雜度可以做到\(O(n)\),既然如此,我們推廣到樹。
我們進行長鏈剖分,每次欽定從重兒子直接轉移,那么我們還需要從輕兒子進行轉移。
不難證明所有輕兒子都是一條重鏈的頂部,轉移時的復雜度是重鏈長度。
那么,復雜度拆分成兩個部分:直接從重兒子轉移\(O(1)\),從輕兒子轉移\(O(\sum len)\)
發現每個點有且僅有一個父親,因此一條重鏈算且僅被一個點暴力轉移,而每次轉移復雜度是鏈長。
所以全局復雜度是\(\sum\)鏈長,也就是\(O(n)\),因此總復雜度就是\(O(n)\)

這樣子寫下來,發現長鏈剖分之后,我們的復雜度變為了線性。
但是注意到復雜度證明中的一點:轉移和鏈長相關。
而鏈長和什么相關呢?深度。所以說對於這一類與深度相關的、可以快速合並的信息,使用長鏈剖分可以優化到一個非常完美的復雜度。如果需要維護的與深度無關的信息的話,或許\(dsu\ on\ tree\)是一個更好的選擇。

這里再額外補充一道題目,似乎都比較簡單:(盡然找不到別的題目了)

【BZOJ3653】談笑風生

【CF1009F】Dominant Indices

【COGS2652】秘術「天文密葬法」

題解啥的直接戳鏈接吧。

三、維護一些奇怪的貪心

BZOJ3252 攻略

題面&題解&代碼

這題比較神仙,對於長鏈剖分的運用也是很妙的,思路可以借鑒一下。

這里不再過細的討論這部分內容。

Ending

根據題目數量的分布,也不難看出長鏈剖分我們用的最多的仍然是維護和深度相關的信息。因為求\(k\)次祖先預處理的復雜度達到了\(O(nlogn)\),成為了復雜度瓶頸,只有當詢問次數非常大的時候,才能體現出長鏈剖分\(O(1)\)回答的優越性。同理,維護一類貪心題,似乎題目也比較固定,並且可以用別的方法解決。只有在維護和深度相關的可以快速合並的信息的時候,時間復雜度可以做到\(O(n)\)級別,而包括輕重鏈剖分在內的一些算法一般都只能做到\(O(nlogn)\)級別,利用長鏈剖分可以做到更大的數據范圍。

咕咕咕,這篇文章就到這里了,主要是\(yyb\)太弱了,難題寫不動,就只能寫寫這些比較簡答的題目。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM