树的直径——定义与求解方法

如果你并没怎么接触过树,那当你看到标题时一定会感到疑惑:树又不是个圆,它哪来的直径?那就请你往下看,学习学习什么是树的直径以及树的直径的求法。

定义

我们先看看直径在圆里面有什么特点:

  • 直径是圆最长的线段。

类似的,我们对树的直径最基础的定义就是:

  • 一棵树内最长的简单路径就是树的直径。

例如下面这棵树

的直径长度是 12 12 12。它的直径有两条: 5 → 3 → 2 → 1 → 7 → 8 5 \to 3 \to 2 \to 1 \to 7 \to 8 532178 4 → 2 → 1 → 7 → 8 4 \to 2 \to 1 \to 7 \to 8 42178

这有什么用?可以求出一些有关于树上最长路的问题。例如下面这个问题:

小 A 每天都要在树形的 X 城晨跑,小 B 负责决定路线。小 A 晨跑时不能重复经过任意一个点,但小 B 想要刁难小 A。请问小 B 应该怎么安排路线才能让小 A 跑得最远?

显然,这个问题的答案就是树的直径。

怎么求?

下面要对一棵 n n n 个点的树求直径。

求树的直径有两种方法,它们各有优缺:

方法1:两遍 dfs

先从任意一个点开始,跑一遍 dfs,找到离这个点最远的点 x x x,再从 x x x 开始,再跑一遍 dfs,找到离 x x x 最远的点 y y y x x x y y y 就是树的一条直径。

至于为啥这样是对的……咱也不知道,咱也不敢问,反正它是对的,自信用就完了。

核心代码:

void dfs(int id,int x,int fa,long long sum){// id表示当前求的是起点还是终点,x表示当前节点,fa表示x的父亲节点,sum表示当前路径长度。 
	int cnt=0;//只有当前节点是叶子结点才可能是最长路径的终点,否则可以继续延伸。 
	for(int i=head[i];i;i=Next[i]){
		int y=ver[i];// 下一个节点 
		long long z=val[i];// 边权 
		if(y!=fa)dfs(id,y,x,sum+z),cnt++;
	}
	if(!cnt&&sum>Max)Max=sum,st[id]=x;// Max表示直径长度,st[1]表示直径起点,st[2]表示直径终点。 
}
st[0]=1;
for(int i=1;i<=2;++i)Max=0,dfs(i,st[i-1],0,0);// 由主函数调用。 

优点:简单,可以求出直径路径。
缺点:遇到负边权就嗝P。

方法二:树形 DP

f [ x ] [ 0 ] f[x][0] f[x][0] 表示以 x x x 为根的子树中以 x x x 为起点的最长边, f [ x ] [ 1 ] f[x][1] f[x][1] 为次长边。答案为:

max ⁡ 1 ≤ i ≤ n { f [ i ] [ 0 ] + f [ i ] [ 1 ] } \max_{1 \leq i \leq n}\{f[i][0]+f[i][1]\} 1inmax{f[i][0]+f[i][1]}

注意, f [ x ] [ 1 ] f[x][1] f[x][1] 不能和 f [ x ] [ 0 ] f[x][0] f[x][0] 在同一棵子树。优先选择 f [ x ] [ 0 ] f[x][0] f[x][0]

  • 运算顺序:从下往上。
  • 状态转移方程:(设 s o n ( x ) son(x) son(x) 表示由 x x x 的子节点构成的集合, W x , y W_{x,y} Wx,y 表示连接 x x x y y y 的边的边权)

f [ x ] [ 0 ] = max ⁡ y ∈ s o n ( x ) { f [ x ] [ 0 ] + W x , y } f[x][0]=\max_{y \in son(x)}\{f[x][0]+W_{x,y}\} f[x][0]=yson(x)max{f[x][0]+Wx,y}

假设上面的方程最终值为 f [ d ] [ 0 ] + W x , d f[d][0]+W_{x,d} f[d][0]+Wx,d,则:

f [ x ] [ 1 ] = max ⁡ y ∈ s o n ( x ) , y ≠ d { f [ x ] [ 0 ] + W x , y } f[x][1]=\max_{y \in son(x),y \not=d}\{f[x][0]+W_{x,y}\} f[x][1]=yson(x),y=dmax{f[x][0]+Wx,y}

核心代码:

void dp(int x,int fa){// x表示当前节点,fa表示x的父亲节点。 
	int d=0;// f[x][0]继承了哪个节点  
	for(int i=head[x];i;i=Next[i]){
		int y=ver[i];// 子节点
		long long z=val[i];// 边权 
		if(y!=fa){
			dp(y,x);
			if(f[y][0]+val[i]>f[x][0])f[x][0]=f[y][0]+z,d=y;
		}
	}
	for(int i=head[x];i;i=Next[i]){
		int y=ver[i];// 子节点 
		long long z=val[i];// 边权 
		if(y!=fa&&y!=d)f[x][1]=max(f[x][1],f[y][0]+z);
	}
	Max=max(Max,f[x][0]+f[x][1]);
}
dp(1,0);// 由主函数调用。 

优点:可以处理负边权。
缺点:真的不好求路径。

例题:

洛谷 P3629 [APIO2010] 巡逻
洛谷 P1099 [NOIP2007 提高组] 树网的核

总结:树的直径指的是一棵树上最长的简单路径,通常用于解决树上最长路径问题。而直径的两种求法各有优缺,有些时候要结合着用。

  • 39
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值