Tips:我们求得每个点的dfs序,然后把这些点按dfs序排序,那么他们所有的LCA肯定是排序后某相邻两点的LCA。
虚树建立的流程(bzoj2286):
1:先把每个询问点按照dfn从小到大的顺序排序。
2:维护一个栈表示当前从根出发的路径。一开始栈中只有一个元素就是根(1)。
3:枚举每一个询问点,尝试插入。
4:如果当前栈中只有一个元素1,就直接插到栈顶返回。
5:否则记当前栈顶元素为sta[upp],计算出sta[upp]和x的LCA,记作lca。
6:如果lca就是sta[upp]。在这个问题中如果一个子树的根是关键点,它的孩子就不用看了。所以这里直接返回。
7:否则判断当前栈维护的路径是否和当前点脱离。
while(upp > 1 && dfn[sta[upp-1]] >= dfn[lca]) Add(sta[upp-1], sta[upp]),upp--;
这里就是把栈回退,一直退到能接上这个点的地方。
8:如果退不到那个点,就把lca当做这个点。
if (lca != sta[upp]) Add(lca, sta[upp]), sta[upp] = lca;
9:最后留下的栈就是剩下的那一条路径,再把这一条路径加上就好了。
while (upp > 1) Add(sta[upp - 1], sta[upp]), upp--;
然后本题的虚树就建好了。这里等于把树上的一些长链压缩了,只保留我们需要的路径上最小值这个有用的信息。
复杂度说明:
考虑我们每两个点最多会增加一个LCA。反证,加上x和y有lca1,x和z有lca2。假设dep[lca1]<dep[lca2],那么y和z的LCA就是lca1.重复出现故矛盾,所以每两个点最多增加一个LCA。所以虚树的总点数是 2 Σ ( k i ) 2\Sigma(k_i) 2Σ(ki)的。
一些技巧:
1:然后对于很多题目建虚树都要建多次,那么 邻接表/vector 的同学都要清空 head/vector ,如果 memset/一个个枚举clear 那肯
定会T的,我往往都会在树形dp时用完边后进行清空,就非常高效了。
loj2219
首先考虑如果给定这k个点怎么办。 f 0 ( i ) f_0(i) f0(i)表示 i i i为根的子树里面关键点的数目, f 1 ( i ) f_1(i) f1(i)表示以 i i i为根的子树里面,以关键点i的最短路径(如果i不是关键点就是一个不可能的值)。 f 2 ( i ) f_2(i) f2(i)表示最长的。
f 0 f_0 f0的转移就是直接加。这里可以直接统计答案。考虑 i i i的子树里面的这些点要形成点对的话一定要向外连,此时的点对数目就是 f 0 ( i ) ∗ ( k − f 0 ( i ) ) f_0(i)*(k-f_0(i)) f0(i)∗(k−f0(i))。每一对对答案的贡献就是 d e p [ y ] − d e p [ x ] dep[y] -dep[x] dep[y]−dep[x]。
f 1 f_1 f1和 f 2 f_2 f2的转移一样的。都是按照顺序枚举子树。
f1[x] = min(f1[x], f1[y] + (dep[y] - dep[x]));
f2[x] = max(f2[x], f2[y] + (dep[y] - dep[x]));
这里统计答案的时候要注意,由于路径可以以 i i i作为LCA,所以要这样统计:
Min = min(Min, f1[x] + f1[y] + (dep[y] - dep[x]));
Max = max(Max, f2[x] + f2[y] + (dep[y] - dep[x]));
也就是考虑一个来接上。类似点分治的一种统计法,不重不漏的。
这里求的虚树稍微和上一题有一点不一样。上一题如果 l c a = = s t a [ u p p ] lca==sta[upp] lca==sta[upp]就直接返回了。这里需要把 x x x入栈再返回。因为上一题的问题,如果 l c a = = s t a [ u p p ] lca==sta[upp] lca==sta[upp]的话,切上面一定比切下面更加优越,所以下边就无所谓了。但是这一题要统计路径信息,所以不能这样省略。