概念
在树上所有最短路径的最大值。
原理
这里介绍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;
}