啃WC課件系列。
LCA講得很好了(雖然一些奇怪的定義讓人摸不着頭腦),為了以后復習方便自己再整理下。
析合樹是用於連續段問題的比較通用的數據結構。
首先定義一下連續段:對於一個長度為\(n\)的排列\(p\),如果對於一個區間\([l,r]\),如果\(p_l,p_{l+1},\dots,p_r\)排序后可以組成值域連續的一段,即\(\max_{i=l}^r p_i-\min_{i=l}^r p_i+1=r-l+1\),那么稱\([l,r]\)為\(p\)的一個連續段。
考慮到連續段有一個非常有用的性質:
對於兩個相交但互不包含的連續段\(x\),\(y\),總有\(x\cup y\)、\(x\cap y\)、\(x\setminus(x\cap y)\)和\(y\setminus(x\cap y)\)都是連續段。
證明比較顯然。
從這個性質出發,我們定義本原連續段為不存在和它相交且互不包含的其它連續段的連續段。容易發現一個非本原連續段一定可以用若干個相鄰的本原連續段的並來表示,並且由於本原連續段互相只有包含關系,因此如果將一個本源連續段看成一個點,那么所有本原連續段實際上可以形成一棵樹形結構。非本原連續段都是由某些連續的兄弟本原連續段組成的。
那么,究竟哪些連續的兄弟本原連續段可以組成非本原連續段呢?
由於所有兒子的並是一個連續段,我們不妨將兒子縮成一個點並離散化,這樣可以簡化為一個排列(不妨叫做兒子排列)。我們只需要知道這個排列的連續段信息即可。
一種合法的情況是,兒子排列的所有非平凡區間都不是連續段,我們將擁有這樣的兒子的點成為析點。
接下來考慮至少存在一個非平凡連續段的情況,那么我們枚舉所有的非平凡連續段,可以發現所有非平凡連續段的並就是整個排列,同時每一個點都會作為若干個連續段的交出現——否則我們就找到了一個非平凡的本原連續段,這是不可能的。
那么我們可以發現:假設兒子排列的長度為\(n\),那么對於\(1\sim n-1\)的每個位置,以它開頭的非平凡連續段至少存在一個,對於\(2\sim n\)的每個位置,以它結尾的非平凡連續段也至少存在一個。那我們考慮對於其中的任意一個區間\([l,r]\),將以\(l,l+1,\dots,n-1\)為開頭的非平凡連續段取並,可以得到\([l,n]\)是連續段,將以\(2,3,\dots,r\)為結尾的非平凡連續段取並,可以得到\([1,r]\)是連續段,將兩個連續段取交就可以得到\([l,r]\)也是連續段。
於是我們可以發現這種情況下,任意一個區間都是一個連續段,我們將擁有這樣的兒子的點成為合點。同時不難發現合點的兒子排列只可能是\(1\sim n\)或者\(n\sim 1\)。我們將長度為\(1\)的連續段也看作合點。
我們還可以發現一些性質:
\(1\)、對於一個析點,它的兒子個數一定\(\geq 4\),因為\(\leq 3\)的排列都至少存在一個非平凡連續段。而對於任意\(n\geq 4\),一定存在有\(n\)個兒子的析點。因為如果\(n\)是偶數,那么\(2,4,6,\dots,n,1,3,5,\dots,n-1\)一定是符合要求的排列,否則\(n\)是奇數,\(4,6,8,\dots,n-1,1,3,5,\dots,n,2\)一定是符合要求的排列。
\(2\)、對於任意一棵包含\(n\)個葉子的樹,如果指定好每個非葉節點的析合性,並滿足析點兒子個數\(\geq 4\),合點兒子個數\(\geq 2\),那么一定存在一個長度為\(n\)的排列滿足其析合樹為這棵樹。可以通過構造符合要求的兒子排列實現。
\(3\)、對於所有區間是否為連續段的情況都相同的排列,它們的析合樹相同。結合性質\(2\),我們可以通過計數析合樹來計數區間連續段情況的不同個數。
最后的問題是析合樹的構造,這里只給出\(O(n\log n)\)的做法,LCA的\(O(n)\)太強了不會。
考慮增量,在前\(1\sim i-1\)項構成的析合樹中插入第\(i\)項。由於接下來的過程保證只會讓已經確定是本原連續段的點向父親連邊,因此可以發現如果一個點已經有父親則其子樹不會再改變(由本原連續段的性質得)。於是我們維護一個棧,表示所有尚未有父親節點的點。
那么我們描述一下增量第\(i\)項的過程:
首先對\(i\)單獨作為一個點,稱為當前點。
接下來不斷重復這些步驟:
首先判斷一下當前點能否成為棧頂的點的新兒子。如果可以則將當前點的父親設為棧頂的點,並取出棧頂的點作為新的當前點。不斷進行這一步直到棧為空或當前點無法成為棧頂的新兒子。
我們來證明能夠合並的當前點一定是一個本原連續段。可以證明棧頂的點一定是合點(因為析點的非平凡兒子前綴一定不是連續段),因此當前點一定是作為兒子排列的最小段或最大段,不妨以是最大段為例,由於之前並未進行合並,因此當前點不存在一個不滿的前綴值域是最大段的前綴,等價於不存在一個不滿的后綴值域是最大段的后綴。由於最大段的前綴在之前出現過,因此如果想要和當前點的不滿后綴拼合得到新的連續段,當前點的后綴的值域必須要是最大段的后綴,而這是不可能的。因此可以合並的當前點一定是本原連續段。
如果當前點不能成為棧頂的兒子,那么先通過某種方法(接下來再具體講)判斷當前點是否可能與棧頂的若干個點合並成一個連續段得到一個新點。如果不可能則直接結束這次增量。否則我們暴力向前掃描直到可以合並成一個連續段為止。
我們來證明合並的這些點一定是本原連續段。可以發現此時“切開”某個兒子的那些前綴的值域不可能是整個連續段值域的前綴(否則會出現新的本原連續段),因此不存在一個切開兒子的后綴是值域的后綴,同理也不存在一個不滿的后綴是值域的前綴,因此實際上一個切開兒子的后綴是無法和之后的任何段形成連續段的,可以得證。
考慮除了判斷方法之外的部分的復雜度。由於每次花費的時間和棧里減少的元素個數成正比,而每次增量最多使得棧內元素個數\(+1\),因此這部分的復雜度為\(O(n)\)。
最后考慮如何判斷是否可能與之前的點合並。考慮預處理數組\(L\),其中\(L_i\)表示以\(i\)右端點的所有連續段的最小左端點。那么加入棧頂的點左端點\(<L_i\),那么一定不會合並。否則如果存在一個點的左端點\(=L_i\),那么一定可以合並到這里。否則一定存在一個不是棧頂的點,其區間為\([l,r]\)且\(l<L_i\leq r\),由連續段性質可知\([r+1,i]\)一定是連續段,因此可以合並。
那么問題在於如何處理\(L\)。考慮維護從左到右枚舉區間右端點,用線段樹維護每個區間的\(\max-\min+1-區間長度\),那么只需要找到一個區間的最左端的值為\(0\)的點,由於\(0\)是可能的最小值,因此實際上是找到最左邊的最小值的位置。考慮對於\(\max\)和\(\min\)的處理可以用單調棧做到共計\(O(n)\)次操作,區間長度也只要在每次右端點更新時更新即可。於是復雜度就是\(O(n\log n)\)。
綜上所述,構建析合樹的復雜度可以做到\(O(n\log n)\)。
\(P.S.\)文章里許多過程或者證明都是我口胡的,假如存在錯誤或者不必要的繁瑣的地方懇請指正。