如何构建虚树?
当我们获得k个关键点后
首先把关键点按dfn(即原树的dfs序)从小到大排序
然后开一个栈
栈的意义(性质):从栈底到栈顶的元素构成(表示)虚树中从上到下的一条链
虚树构建过程:
依次枚举关键点x
当栈为空或栈中只有一个元素(即top<=1,top从0开始),直接把x压入栈中(break/return)
否则令lca=LCA(x,stk[top])
如果lca=stk[top]
说明x应该接在stk[top]的下面(在虚树中),所以直接把x压入栈中(break/return)
如果lca!=stk[top]
说明x和stk[top]分属lca的两棵不同的子树,而且stk[top]所在的子树中已经构建完成了,所以我们把lca的stk[top]所在的那棵子树弹栈,在弹栈的过程中建边(单向边),直到 dfn[stk[top]]<=dfn[lca]<=dfn[stk[top-1]] (即lca在栈顶的两元素的路径上) 或 栈中元素小于2的时候停止弹栈,并判断lca是否等于stk[top]
若不等,先从lca向stk[top]连边,然后弹出栈顶,压入lca,再压入x
否则直接压入x
枚举关键点结束后,若栈中的元素超过2个(即top>1),就不断从stk[top-1]向stk[top]连边,并弹出栈顶。
到此,虚树构建完成,可以愉快地DP了。
总之,这个过程看似复杂,但只要想着要始终维护栈的性质(从栈底到栈顶的元素构成虚树中从上到下的一条链)就不容易打错了。写的时候可以画个图,让自己思路清晰
上马:
void insert(int x){
if(top==1){stk[++top]=x;return;}
int lca=LCA(stk[top],x);
if(lca==stk[top]){stk[++top]=x;return;}
while(top>1&&dfn[stk[top-1]]>=dfn[lca]){
ADD(stk[top-1],stk[top]); // 单向
--top;
}
if(lca!=stk[top]){
ADD(lca,stk[top]);
stk[top]=lca;
}
stk[++top]=x;
}
int main(){
...
sort(x+1,x+k+1,cmp); //按dfn从小到大
top=0;ans=0;
stk[++top]=1;
for(int i=1;i<=k;++i){
if(x[i]==1)continue;
insert(x[i]);
}
while(top>1)ADD(stk[top-1],stk[top]),--top;
...
}