树的直径

定义

树上任意两点间最短距离的最大值。

两种做法

dfs(bfs)
算法流程

s t e p   1 : step\space 1: step 1:随便选取一个点 p p p,dfs找到离 p p p最远的点 q q q

s t e p   2 : step\space 2: step 2: q q q进行同样的dfs,树上所有点离 q q q的最短距离则为该树的直径。

时间复杂度: O ( n ) O(n) O(n)

正确性证明

我们假设离 P P P最远的点是 Q Q Q

1. 1. 1. P P P在直径上,易得树的直径为 P Q PQ PQ

2. P 2.P 2.P不在直径上,我们假设树的直径为 A B AB AB

首先,我们讨论 A B , P Q AB,PQ AB,PQ有交点的情况,我们设交点为 C C C

由于距离 P P P最远的点为 Q Q Q,那么我们可得 C Q + B C ≤ C Q + P C CQ+BC\le CQ+PC CQ+BCCQ+PC

B C ≤ P C BC\le PC BCPC

那么我们有 A B = A C + B C ≤ A C + P C AB=AC+BC\le AC+PC AB=AC+BCAC+PC,与 A B AB AB为直径矛盾,故不成立。

然后是 P Q , A B PQ,AB PQ,AB无交点的情况。


如图所示,我们设 N N N P Q PQ PQ上一点, M M M A B AB AB上一点,且有一条路径从 N N N M M M

我们用和上文相同的方法,得 P Q ≥ P N + N M + M B PQ\ge PN+NM+MB PQPN+NM+MB,即 N Q ≥ N M + M B NQ\ge NM+MB NQNM+MB

所以 N Q + M N ≥ M B NQ+MN \ge MB NQ+MNMB A B ≤ A Q AB\le AQ ABAQ,也与 A B AB AB为树的直径矛盾。

综上所述,我们的算法是正确的。

模板题:poj1985

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int first[200005],nxt[200005],to[200005],w[200005],tot=0;
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
int maxn=-1,pos=0;
void dfs(int u,int fa,int dis){
	if(dis>maxn){
		maxn=dis;
		pos=u;
	}
	for(int e=first[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fa)  continue;
		dfs(v,u,dis+w[e]);
	}
}

int main(){
	int n=Read(),m=Read();
	for(int i=1;i<=m;i++){
		int x=Read(),y=Read(),z=Read();
		Add(x,y,z);
		Add(y,x,z);
	}
	dfs(1,0,0);
	maxn=-1;
	dfs(pos,0,0);
	cout<<maxn<<endl;
}
DP
算法流程

s t e p   1 : step\space1: step 1:找到每个点子树中的最长链与次长链 f [ i ] 与 g [ i ] f[i]与g[i] f[i]g[i]

s t e p   2 : step\space 2: step 2:求出每个点 f [ i ] + g [ i ] f[i]+g[i] f[i]+g[i]的最大值

时间复杂度: O ( n ) O(n) O(n)

正确性证明

一棵树的直径必定是一条链,那么我们可以想象将一条链折叠,就变成了这样:

那么我们的思路就很清晰了,枚举每一个点,找到其子树中的最长链和次长链(注意不要有重合部分),然后对于所有点求一个最大值即可。

D P DP DP是我们采用一个巧妙的方法,用一个 d d d数组来记录一个最大值,如果我们搜到的链长大于我们的 d d d,那么 d d d就成为了我们的次大值,更新 m a x max max即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
int first[200005],nxt[200005],to[200005],w[200005],tot=0;
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
int d[200005],maxn=-1;
void dp(int u,int fa){
	for(int e=first[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fa)  continue;
		dp(v,u);
		maxn=max(maxn,d[u]+d[v]+w[e]);
		d[u]=max(d[u],d[v]+w[e]);
	}
}
int main(){
	int n=Read(),m=Read();
	for(int i=1;i<=m;i++){
		int x=Read(),y=Read(),z=Read();
		Add(x,y,z);
		Add(y,x,z);
	}
	dp(1,0);
	cout<<maxn<<endl;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值