浅谈树的直径

浅谈树的直径


定义:

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

求解:

  树的直径存在两种求解方式均为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
using namespace std;
#define int long long
typedef pair<int,int> p;
priority_queue

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;ii) { 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以及直径中点相关知识。

 

转载于:https://www.cnblogs.com/knife-rose/p/11189707.html

你可能感兴趣的:(浅谈树的直径)