当前位置 博文首页 > A_Bright_CH的博客:树的直径—求法

    A_Bright_CH的博客:树的直径—求法

    作者:[db:作者] 时间:2021-09-21 21:03

    先给出树的直径的定义:一棵树中任意两点间的路径叫“链”,最长的链称作树的直径。树的直径并没有明确指明是一个距离还是一段路径,其实两者都可以用“树的直径”来称呼。
    具体求树的直径我们有两种方法,一种用DP,一种用dfs,它们的时间复杂度都是O(N)。

    ?

    树形DP法

    设数组D[i]表示从节点i出发往子树方向走的最长距离,F[i]表示以节点i为转折点的最长链。显然ANS=max{F[i]}。
    仔细考虑它们的联系,F[i]=最大的D[i]+次大的D[i]。如果真要记录两个D,代码会复杂很多。再仔细考虑一下实现过程,当在x节点让y去dfs的时候,D[y]最终会得到一个最大深度。在之前D[x]=\max\left\{D[x],D[y]+e[k].c\right\},D[x]中存的是通向另一个子节点的最大深度。它们的关系不有点像最大的D和次大的D吗?只不过这个次大的D不是严格上的次大罢了。但是根据两两间的匹配原则,A-B和B-A是一回事,就是说这两者早晚会是那个最大和次大的关系。
    所以在dfs中要这样写:

    void dp(int x,int fa)
    {
    ?? ?for(int k=last[x];k;k=e[k].next)
    ?? ?{
    ?? ??? ?int y=e[k].y;
    ?? ??? ?if(y==fa) continue;
    ?? ??? ?dp(y);
    ?? ??? ?ans=max(ans,d[x]+d[y]+e[k].c);//这里其实是ans=max(ans, f[x]=max(f[x],d[x]+d[y]+e[k].c) );不过这样的f[x]没有意义,舍弃了
    ?? ??? ?d[x]=max(d[x],d[y]+e[k].c);
    ?? ?}
    }

    两次BFS/DFS

    第一次找出一个离x最远的节点,第二次从那个节点开始找一个最深的节点。
    可以证明这样找出来的一定是树的直径。不妨不这个点调整到根节点。先证明第一步,第一次DFS出来的一定是直径的一端。假设我们找到了一个最深的点x但它不是直径的一端,假设直径的一端在根节点的另一个子树里,节点记为y,那么显然不成立。因为从y出发最大的向上距离是到根,从根出发最大的向下距离是到x,所以x一定是直径的一端。假设直径的一端l与x处于同一棵根子树,若直径的另一端r也在这棵根子树中,如果l先向上到底lca(l,r),然后拐入r,明显不如x出发到lca(l,r)再到r长,因为x更深。如果l,r不在一棵根子树中,r一定会优先选择更深的x。

    int son[maxn],d[maxn];//d记录最大深度,son[x]记录最大深度的那个节点
    void dfs(int x,int fa)
    {
    ? ? vis[x]=T;
    ? ? d[x]=0;son[x]=x;
    ? ? for(int k=last[x];k;k=e[k].next)
    ? ? {
    ? ? ? ? int y=e[k].y;
    ? ? ? ? if(y==fa) continue;
    ? ? ? ? dfs(y);
    ? ? ? ? if(d[y]+e[k].c>d[x])
    ? ? ? ? {
    ? ? ? ? ? ? d[x]=d[y]+1;
    ? ? ? ? ? ? son[x]=son[y];
    ? ? ? ? }
    ? ? }
    }
    int main()
    {
    ?? ?int s,e;
    ?? ?dfs(1,0);s=son[1];
    ?? ?dfs(s,0);e=son[s];
    }

    比较&总结

    两次BFS/DFS的做法优于树形DP之处在于它可以求出树上的最长链到底是那条,具体的给出一个起始点S和终止点E,这样在一次遍历后我们可以找出那条实实在在的链到底是什么,相当于求出了方案。但是两次BFS/DFS貌似不能解决带负权边的问题,这时只能用树形DP了。

    cs
    下一篇:没有了