题目链接:
[WC2018]通道
题目大意:给出三棵n个节点结构不同的树,边有边权,要求找出一个点对(a,b)使三棵树上这两点的路径权值和最大,一条路径权值为路径上所有边的边权和。
我们按照部分分逐个分析有1、2、3棵树时的做法。
首先说一个结论,在下面讲解中能应用到:
对于一棵树T1的直径两端点为u,v,对于另一棵树T2的直径两端点为x,y,如果将两棵树合并(即将两棵树中的各一个点连边)那么新树的直径的两端点一定是u,v,x,y中的两个。
证明见树的直径及其性质与证明。
一、一棵树
这个很好做吧,直接求树的直径就好了。
二、两棵树
我们假设答案点对是(a,b),那么ans(a,b)=dis1(a,b)+dis2(a,b)。其中dis1,dis2分别表示两棵树上的两点距离。
我们将第一棵树的答案拆开表示:ans(a,b)=dep1(a)+dep1(b)-2*dep1(lca)+dis2(a,b)。其中dep1为第一棵树中的该点深度。
可以发现dep1(lca)与a,b无关,我们对于第二棵树上的点x建立一个点x'与x相连,边权为dep1(x)。
这样忽略dep1(lca),答案就是Tree2中的直径。
现在计算dep1(lca)对答案的影响,我们可以对于Tree1树形DP,每个点存子树中所有点在Tree2中的形成的直径的两端点及直径长度,回溯时将每个子节点的答案合并到父节点上,这时Tree2中的直径减掉这个父节点深度*2即可更新答案。合并时利用上面讲到的结论六种情况枚举讨论,更新答案时因为要保证直径两端点不在这个父节点的同一棵子树内,所以有四种情况可以更新答案。这里Tree1中一个点子树中所有点在Tree2中形成的直径可以看作是这些点在Tree2上两两之间路径包含的所有点组成的树的直径,而合并时相当于在Tree2上将两个可能有交集的子树拼接到一起,上述结论依旧成立。
时间复杂度O(nlogn)。
三、三棵树
三棵树时答案为ans(a,b)=dis1(a,b)+dis2(a,b)+dis3(a,b)。
我们对于第一棵树进行边分治,将多叉树转二叉树,对于每次分治的联通块,以中心边为界将联通块分成两部分。(边分治具体实现参见边分治讲解)
同时将第二棵树的答案拆开表示:ans(a,b)=d1(a)+d1(b)+val+dep2(a)+dep2(b)-2*dep2(lca)+dis3(a,b)。其中d1为该点到当前分治中心边的距离,val为分治中心边的长度。
同样按照两棵树时的做法,对于第三棵树上的点x建一个点x'与x相连,边权为d1(x)+dep2(x)。
每次对Tree1进行边分治时将分治联通块中的点在Tree2上建虚树,按照两棵树时的做法在虚树上树形DP。
因为还需要保证在Tree3中找到的直径的端点在Tree1中分别位于分治中心边的两端,所以每次边分治将分治中心一边的点标号为1,另一边的点标号为2。
在树形DP是每个点分别维护子树中标号为1/2的点组成的直径的两端点及直径长度,合并同样各种情况讨论一下并更新答案即可。
时间复杂度为O(nlogn^2),RMQ求LCA+基数排序可以将时间复杂度降为O(nlogn)。
注意:因为边权可能为0,所以求lca时不能比较真实深度(即带权深度)而要比较不带权深度。
这道题也可以用点分治来代替边分治,但边分治能将每次分治联通块中的点恰好分成两部分,而点分治对于分治中心的处理比较麻烦,所以建议写边分治。
亲测用树剖求LCA比用RMQ求LCA快,也不知道为什么...
调了好几天,也写了好几个版本的,最后还是都坚持调出来了,虽然很长,但还是都放上来,供读者选择。
其中edge_partation为第一棵树即边分治的树;virtual_tree为第二棵树即建虚树的树;value_tree为第三棵树即需要求直径的树。
RMQ求LCA+非递归树形DP
#include
#include
RMQ求LCA+递归树形DP
#include
#include
树剖求LCA+递归树形DP
#include
#include