学习笔记:树的直径

概念

在树上所有最短路径的最大值。

原理

这里介绍2种方法求树的直径。

两次DFS(BFS)法
我们任意从树上某个点出发,找到到它最远距离的点,然后再以这个点为起点,找到离这个点最远的点,这两点之间的距离即是树的直径。

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

const int N=1e5+5;
int n,Max,p,dis[N];
ll ans;
int first[N],nex[N],to[N],w[N],tot;

void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

void dfs(int x,int fa)
{
    
    
	for(int i=first[x];i;i=nex[i])
	{
    
    
		int y=to[i];
		if(y==fa) continue;
		dis[y]=dis[x]+w[i];
		if(dis[y]>Max)
		{
    
    
			Max=dis[y];
			p=y;
		}
		dfs(y,x);
	}
}

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
    
    
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	dis[1]=0;
	dfs(1,0);
	
	dis[p]=0;  //初始化
	Max=0;
	dfs(p,0);
	
	cout<<Max;
	return 0;
}

朴实无华
树形dp法
它是一棵树,且求最远距离,我们考虑dp。
dp1[x],dp2[x]表示x的子树到x的最大距离和次大距离,先遍历一遍所有点,回溯时更新dp1和dp2,那么以它为根的子树的直径等于最大+次大,如果大于ans,更新ans。最后的结果就是树的直径。
代码实现:

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

const int N=5e5+5,M=1e6+5;
int n,dp1[N],dp2[N],ans;
int first[N],nex[M],to[M],w[M],tot;

void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

void dfs(int x,int fa)
{
    
    
	for(int i=first[x];i;i=nex[i])
	{
    
    
		int y=to[i];
		if(y==fa) continue; //不能找回去
		dfs(y,x);
		if(dp1[x]<dp1[y]+w[i]) //能更新最大值,那么更新最大值,次大值更新为当前的最大值
		{
    
    
			dp2[x]=dp1[x];
			dp1[x]=dp1[y]+w[i];
		}
		else if(dp2[x]<dp1[y]+w[i])//能更新次大值就更新
		{
    
    
			dp2[x]=dp1[y]+w[i];
		}
		ans=max(ans,dp2[x]+dp1[x]);//当前的树的直径
	}
}

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
    
    
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	
	dfs(1,0);
	
	cout<<ans;
	return 0;
}

例题传送门poj1985

值得注意的是:当边权为负的时候,2次dfs方法不行,直径的两个端点无法找到。

深入

传送门luoguP4408

思路:
路线是从家到a点,肯定要找不到路程才更大,所以在从a到b点。
那么a到b的路程肯定是树的直径才能最长。
由于我们还要求端点,所以用2次dfs搜。
关键:我们要如何找到家到a或b的最近的最大距离呢?
由题可知,到家的最大距离受到了a,b两个点同时限制,我们在第2次dfs时已经得到了所有点到a的最短距离。那我们不妨再从b点搜到b的所有点的最短距离,比较与到a点的距离,如果比a更短,就更新Max,比它长就不管,那我们可以放到一起来写。
代码实现:

#include<bits/stdc++.h>
using namespace std;
#define int long long //偷懒,不开longlong会爆 

const int N=2e5+5,M=4e5+5;
int n,m,p,dis[N];
int first[N],nex[M],to[M],w[M],tot;
int ans,Max;

inline void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

inline void dfs(int x,int fa)
{
    
    
	for(int i=first[x];i;i=nex[i])
	{
    
    
		int y=to[i];
		if(y==fa) continue;
		dis[y]=min(dis[y],dis[x]+w[i]); //2,3 第2次是更新到该点的最短距离
		//第3次是为了比较到哪个端点更近 
		if(Max<dis[y]) 
		{
    
    
			Max=dis[y]; //2,3 更新答案,到端点最短的最大值 
			p=y;//1,2 找点 
		}
		dfs(y,x);
	}
}

signed main()
{
    
    
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
    
    
		int x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	dfs(1,0); //1 表示找直径的一个端点 
	//编号对应在dfs中用到的功能 
	memset(dis,0x3f,sizeof(dis));
	Max=0,dis[p]=0;
	dfs(p,0); //2 找另一个,并更新其他点到该点的最短距离 
	ans=Max,Max=0,dis[p]=0;//注意:没有初始化dis 
	dfs(p,0); //3 从另一端点找 
	cout<<ans+Max;//树的直径加上家到某个端点的最大值 
	return 0;
}

Guess you like

Origin blog.csdn.net/pigonered/article/details/120888025