浅谈树的直径

一、定义

        我们将一棵树T=(V,E)的直径定义为max(u,v) (u,v∈V),也就是说,树中所有最短路径距离的最大值即为树的直径。(就是树中的最长路径的长度)


二、求解树德直径

        求得树的直径有两种方法,一种是两边 bfs (或者两边dfs)就能搞定的算法。还有一种就是树形DP。我们先来了解第一种,就是两遍dfs(或bfs)的算法。

        引理:对于树上任意一点 P,找到离它最远的节点 Q。在找到离节点 Q 最远的节点 W。路径WQ的长度就是树的直径。

        下面我们来证明这个引理。我们将证明分为3种情况。

                1):点 P 本身就在直径上,所以很容易知道 Q 也在直径上且为直径的一个端点。

                2):点 P 不在树的直径上,但假设存在不是 WQ 的树的直径 AB 与路径 PQ有一个交点 C,如下图。反证法:因为 PQ 为最远,所以 PC + CQ > PC + CA 进而可以得到 CQ > CA。不等式两边同时加上BC,得到 CQ + BC > CA + BC = AB。 这个结论与 AB 为直径的假设不符,故假设不成立。 

                3):点 P 不在树的直径上,并且假设存树的直径不是 WQ 而是 AB,且 AB 与 PQ 没有交点,如下图。因为 PQ 为最远,所以 NP + NQ > NQ + MN + MB,进而得到 NP > MN + MB。易得 NP + MN > MB。不等式两边同时加上 AM 得 NP + MN + AM > MB + AM = AB。与 AB为树的直径的前提相矛盾。所以假设不成立。

         综上所述,引理成立,证毕。

         既然我们已经知道了引理,那么要求出树的直径也就很简单了,两边 dfs(或 bfs)就行了。代码如下:

#include<bits/stdc++.h>
using namespace std;

#define MAXN 100100
#define MAXM 2 * MAXN

int n = 0; int m = 0;

int tot = 0;
int first[MAXN] = { 0 };
int   nxt[MAXM] = { 0 };
int    to[MAXM] = { 0 };
int value[MAXM] = { 0 };

void add(int x, int y , int weight){
	nxt[++tot] = first[x];
	first[x] = tot; 
	to[tot] = y;
	value[tot] = weight;
}

int p = 0; int ans = 0;
int dis[MAXN] = { 0 };
void dfs(int x, int fa){
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa){
			continue;
		}
		dis[y] = dis[x] + value[e];
		if(ans < dis[y]){
			ans = dis[y];
			p = y;
		}
		dfs(y, x);
	}
}

int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++){
		int x = 0; int y = 0; int w = 0;
		scanf("%d%d%d", &x, &y, &w);
		add(x, y, w);
		add(y, x, w);
	}
	
	ans = 0;
	dis[1] = 0;
	dfs(1, 0);
		
	ans = 0;
	dis[p] = 0;
	dfs(p, 0);
	
	printf("%d\n", ans);
	
	return 0;
}


        接下来是第二种方法:树形DP(玄学的方法qwq):

        我们需要俩数组,一个 f1[MAXN],一个f2[MAXN]。分别表示在以 i 节点为根的子树中,i 到叶节点的距离的最大值和次大值。然后就是显而易见玄学的动归方程了(其中w[i][j] 表示 i 到 j 的边权):

                                        if(f1[i] < f1[j] + w[i][j])   f2[i] = f1[i], f1[i] = f1[j] + w[i][j]

                                        else if(f2[i] < f1[j] + w[i][j])   f2[i] = f2[j] + w[i][j]

        原理其实很简单(说他玄学是因为不知道是谁那么聪明想出来的这玩意儿)。就是说我们尝试去更新 f1[i] ,如果可以更新,那我们让 f2[i] = f1[i] 刚才的最大值为现在的次大值,然后再更新f1[i]。否则,我们尝试更新 f2[i] 如果能更新,就更新。如果不能,就不管他。

        我们得到这俩数组之后,树的直径很显然就是 \max_{i=1}^{n} (f1_i + f2_i) 了。

 

        然后就是代码:

#include<bits/stdc++.h>
using namespace std;

#define MAXN 1001000
#define MAXM 2 * MAXN

int n = 0; int m = 0;

int tot = 0;
int first[MAXN] = { 0 };
int   nxt[MAXM] = { 0 };
int    to[MAXM] = { 0 };
int value[MAXM] = { 0 };

void add(int x, int y, int weight){
	nxt[++tot] = first[x];
	first[x] = tot;
	to[tot] = y;
	value[tot] = weight;
}

int ans = 0;
int f1[MAXN] = { 0 };
int f2[MAXN] = { 0 };

void dp(int x, int fa){
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa){
			continue;
		}
		dp(y, x);
		if(f1[x] < f1[y] + value[e]){
			f2[x] = f1[x];
			f1[x] = f1[y] + value[e];
		}
		else if(f2[x] < f1[y] + value[e]){
			f2[x] = f1[y] + value[e];
		}
		ans = max(ans, f1[x] + f2[x]); 
	}
}

int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n-1; i++){
		int x = 0; int y = 0; int w = 0;
		scanf("%d%d%d", &x, &y, &w);
		add(x, y, w); add(y, x, w);
	}
	
	dp(1, 0);
	
	printf("%d\n", ans);
	
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值