浅谈树的直径

拈花ヽ惹草 提交于 2020-01-12 20:23:04

浅谈树的直径


定义:

  树的直径指树上最长链(最远点对)

求解:

  树的直径存在两种求解方式均为O(n)复杂度,其各有优劣

1.贪心法

  任取一点作为起点,找到树上距离该点的最远点,记作st,再以st为起点,找到树上距离st最远的点,记作ed,st至ed即为树的直径。

  (找最远点操作DFS和BFS均可)

  优点:起点与终点方便获得。

   缺点:负边权就GG。

inline void dfs(int now,int fa,int deep)
{
    if(deep>res)
    {
        res=deep;
        ed=now;
    }
    f[now]=fa;
    dep[now]=deep;
    for(int i=head[now];i;i=a[i].nxt)
    {
        int t=a[i].to;
        if(t==fa) continue;
        dfs(t,now,deep+a[i].val); 
    }
}
dfs(1,0,0);
res=0;
st=ed;
dfs(st,0,0);

 

2.树型DP

  任取一点作为起点,记录树上每一点向下的最远距离和非严格次远距离,直径长度即为每一点二者之和的最大值。

  优点:能处理负边权。

  缺点:起点终点难以记录。

 

inline void dfs(int now,int fa)
{
    for(int i=head[now];i;i=a[i].nxt)
    {
        int t=a[i].to;
        if(t==fa) continue;
        dfs(t,now);
        res=max(res,dp[now]+dp[t]+a[i].val);
        dp[now]=max(dp[now],dp[t]+a[i].val);
    }
}

 

性质:

  1.直径两端点一定是叶子节点。

  2.距任意点最远点一定是直径的端点,据所有点最大值最小的点一定是直径的中点。

  3.两棵树相连,新直径的两端点一定是原四个端点中的两个

  4.两棵树相连,新直径长度最小为max(max(直径1,直径2),半径1+半径2+新边长度  )  (设k为直径中最接近中点的节点,半径=max(tot-d[k],d[k]))

  5.一棵树上接一个叶子结点,直径最多改变一个端点

  6.若一棵树存在多条直径,多条直径交于一点,且交点是直径的严格中点(中点可能在某条边内)

例题:

  前言:树的直径姿势点主要考察各种性质的应用,灵活运用各种性质即可切题。

  洛谷P3629APIO 2010巡逻

  题目概述:有一棵树,要求我们在树上加上1~2两条边,使遍历每一条路时经过的路径最短,关于路径,有以下几个要求

  1.新建的边必须正好经历一次

  2.新边可以是自环

  3.遍历后回到起点

  分析:既然要回到起点,在无环情况下,每条边必然要走两遍,路径长度为2*n-2;

  对于k=1的情况,一个贪心的想法必然是让树上最长边形成一个环,所以求一遍树的直径即可,设树的直径长度为res,路径长度变为2*n-2-res+1;

  而对于k=2的情况,在加完第一条边后,必然要找第二条最长的边

  然而在找第二条最长边的时候我们发现:若第二条边与第一条边有重叠部分,那么重叠部分就要再走一次,相当于减少了第一次的贡献

  同时,由于建立新边是为了防止一条边被第二次遍历,第一条最长边选中的部分本身遍不用第二次遍历,所以该边对于最长边的贡献本就应是零

  综合本来的贡献和减少的第一次贡献,改变对于第二次求最长边的贡献应该是边权的负值XD,此时我们应该将第一条最长边的边权全部取反,再找一条最长边。

  在取反后,树上遍有了负边权,我们需要采取树型DP求第二条最长边,而且由于需要取反第一条最长边,我们需要第一条最长边的路径,第一条适合用dfs求最长边

  总结:该题目综合考察了DFS求树的直径和树型DP求树的直径的优点。

 

  洛谷P2491 SDOI2011消防

  题目概述:在一颗树上选取一段区间,长度小于m,使得所有节点到该区间的最大值最小。

  分析:由性质2:据任意点最大值最小的点一定是直径的中点  可知:中点必然入选。

  再考虑将中点扩展为区间:据任意点最远点一定是直径的中点可知,若扩展为区间,在直径上扩展一定是最优的

  可反证:若存在另一点到直径上一点距离大于直径端点到直径上一点距离,那么将会产生新的直径。

  现在问题转化为在直径上选取一段区间长度小于m,使得任意一点到这段区间的最大值最小;

  我们可以预处理出其他节点到达直径的最远距离,一次为l,以直径长度为r,二分答案(判断根据性质2只需判断该区间到直径端点距离是否小于mid)

  近一步我们可以双指针法直接求答案

  注意一个特判:若m>=直径长度,直接输出l。

  总结:重点考察了性质2和相关应用。

 

  洛谷P3761 TJOI2017城市

  题目概述:在树上断开一条边连到别处,使得新树的直径最小,求直径最小值。

  分析:为了改变新树直径,断开的边必然在原树直径上,找到一条直径,枚举断开哪一条边。(若有多条直径也没法管)

  根据性质4:新树直径最小值为max(max(子树1直径,子树2直径),子树1半径+子树2半径+断开边长度),在所有新树直径中取最小值即可。

  总结:考察性质4运用。

  

  洛谷P3304 SDOI2013直径

  题目概述:给定一棵树,求所有直径都共同经过的边的数量。

  分析:根据性质6:所有直径必然交于中点,我们只要分析中点在边内和在节点上的情况。

  情况1:中点在边内:ans=1,对于该边的两个节点做一次以下操作:

      处理出每个点能够到达的最大深度,记作d[now],若存在0个或1个以上的子节点满足d[t]+边权==d[now]则该节点向下不存在直径必经边。

      若仅存在1个子节点d[t]+边权==d[now],++ans,再对该子节点做一个相同操作。

  情况2:中点在点上:处理出每个点能够到达的最大深度,扔进堆里,取前三大的点比较,设为a,b,c。

  若不存在c或a=b>c,ans=2,a,b,做一次上述操作。

  若a=b=c,则不存在必经边。

  该题目略难以理解(至少弱鸡作者这么认为的QWQ),所以将代码放上,但是由于各大OJ该题数据普遍过水,不存在中点在节点上的数据,作者并不能保证代码的正确性,若有大神发现不妥之处欢迎指正

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int,int> p;
priority_queue<p> q;
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,st,res,tot,now,ans;
int f[200010],dep[200010],d[200010];
int head[200010],cnt;
struct point 
{
    int nxt,to,val;
}a[500010];
inline void add(int x,int y,int z)
{
    a[++cnt].nxt=head[x];
    a[cnt].to=y;
    a[cnt].val=z;
    head[x]=cnt;
}
inline void dfs(int now,int fa,int deep)
{
    f[now]=fa;
    dep[now]=deep;
    d[now]=0;
    if(deep>res)
    {
        res=deep;
        st=now;
    }    
    for(int i=head[now];i;i=a[i].nxt)
    {
        int t=a[i].to;
        if(t==fa) continue;
        dfs(t,now,deep+a[i].val);
        d[now]=max(d[now],d[t]+a[i].val);
    }
}
inline void work(int x,int y)
{
    dfs(x,y,0);
    while("miao")
    {
        int t=0,sum=0;
        for(int i=head[x];i;i=a[i].nxt)
        {
            if(a[i].to==y) continue;
            if(d[x]==d[a[i].to]+a[i].val)
            {
                t=a[i].to;
                ++sum;
            }
        }
        if(sum^1) break;
        y=x;
        x=t;
        ++ans;
    }
}
signed main()
{
    n=read();
    for(int x,y,z,i=1;i<n;++i)
    {
        x=read(),y=read(),z=read();
        add(x,y,z);
        add(y,x,z);
    }
    dfs(1,0,0);
    res=0;
    dfs(st,0,0);
    printf("%lld\n",res);
    while(2*dep[f[st]]>=res) st=f[st];
    if(2*dep[st]==res)
    {
        dfs(st,0,0);
        for(int i=head[st];i;i=a[i].nxt)
        {
            int t=a[i].to ;
            q.push(p(d[t]+a[i].val,t));
        }
        p a=q.top();
        q.pop();
        p b=q.top();
        q.pop();
        if(!q.empty())
        {
            ans=2;
            work(a.second,st);
            work(b.second,st);
        }
        else
        {
            p c=q.top();
            if(c.first==a.first)
            {
                ans=0;
            }
            else
            {
                ans=2;
                work(a.second,st);
                work(b.second,st);
            }
        }
    }
    else 
    {
        ans=1;
        work(st,f[st]);
        work(f[st],st);
    }
    printf("%lld\n",ans);
return 0;
}    

  总结:考察性质6以及直径中点相关知识。

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!