虛樹
虛樹看起來很簡單的樣子。
事實上也的確很簡單。
我們先來知道一下虛樹是用來干什么的。
對於一個問題,我們知道他可以做樹型\(dp\)
\(dp\)的類型大致是給你\(k\)個關鍵點,而\(dp\)的結果與這些關鍵點有關系
有\(m\)組詢問,需要你對於每組詢問進行回答。
並且有條件\(\sum k\)與\(n\)是同階的。
如果每次對於所有點都做一遍\(dp\),復雜度達到了\(O(nm)\)
所以,我們需要把復雜度往\(\sum k\)上靠。
這樣,就有了虛樹。
我們仔細想想,每次\(dp\)的時候是否真的所有點都需要計算呢?
答案顯然是不。我們只需要計算那些對於答案有影響的點。
再來思考一下哪些點對於答案有影響呢?
關鍵點顯然是有影響的。
同時,\(dp\)的時候顯然需要合並答案,因此,關鍵點之間的\(LCA\)也是有影響的
所以,我們構建的虛樹包括兩種點:關鍵點,\(LCA\),還需要一個毫無關系的節點作為根節點來合並最后的答案。
因此,構建虛樹就相當於把這些點拿出來,然后按照原樹中的方式連接好就行了。
怎么做呢?
先考慮怎么求出\(LCA\),顯然不可能\(O(k^2)\)去求
事實上,我們只需要把關鍵點按照\(dfs\)序(\(dfn\))排序,然后把相鄰點的\(LCA\)求出來就好了
這樣子,我們最多可能拿到\(2k\)個點(所以注意一下數組的大小,是\(2\)倍)
現在考慮怎么構建。
把現在所有的點按照\(dfn\)排序,維護一個棧,棧中的點全部在同一條鏈上,節點的深度從棧頂到棧底遞減。
對於新加入的一個節點,檢查新的節點是否在棧頂節點的子樹中,
如果在,直接加入棧中,仍然滿足鏈的性質,並且棧頂就是當前點在虛樹上的父親。
證明?因為已經按照\(dfn\)排序,因此一條鏈肯定從上至下依次出現。
如果當前點不在棧頂節點的子樹中,證明當前棧頂節點的子樹中已經沒有虛樹中的點了
直接把它彈出來,繼續檢查即可。
這樣我們就構建出了虛樹了。
大致的代碼如下:
sort(&p[1],&p[K+1],cmp);
for(int i=K;i>1;--i)p[++K]=LCA(p[i],p[i-1]);p[++K]=1;
sort(&p[1],&p[K+1],cmp);K=unique(&p[1],&p[K+1])-p-1;
for(int i=1,top=0;i<=K;++i)
{
while(top&&low[S[top]]<dfn[p[i]])--top;
Add(S[top],p[i],0);S[++top]=p[i];
}
至於虛樹要怎么用???
那就因題而異了。
