【定义】
这棵树中距离最远的两个结点之间相隔的距离。注意:是任意两个结点的最远距离,不是树的深度,显然,树的直径可以有很多条。
【两次dfs或者(两次bfs)】
思想:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是是的直径
证明如下:
①若P已经在直径上,根据树的直径的定义可知Q也在直径上且为直径的一个端点
②若P不在直径上,我们用反证法,假设此时WQ不是直径,AB是直径
--->若AB与PQ有交点C,由于P到Q最远,那么PC+CQ>PC+CA,所以CQ>CA,易得CQ+CB>CA+CB,即CQ+CB>AB,与AB是直径矛盾,不成立,如下图(其中AB,PQ不一定是直线,画成直线是为了方便):
--->若AB与PQ没有交点,M为AB上任意一点,N为PQ上任意一点。首先还是NP+NQ>NQ+MN+MB,同时减掉NQ,得NP>MN+MB,易知NP+MN>MB,所以NP+MN+MA>MB+MA,即NP+MN+MA>AB,与AB是直径矛盾,所以这种情况也不成立,如下图:
struct node
{
int v;//终端点
int next;//下一条同样起点的边号
int w;//权值
}edge[N*2];///无向边,2倍
int head[N];///head[u]=i表示以u为起点的所有边中的第一条边是 i号边
int tot; ///总边数
void add(int u,int v,int w)
{
edge[tot].v=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot++;
}
int dp[N][3];
//正向最大距离(dp[i][0])和正向次大距离(dp[i][1])
void dfs1(int u,int fa) ///求出每个节点子树下的最大距离和次大距离
{
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v= edge[i].v;
dfs1(v,u);
if(fa==v) continue;
int t=dp[v][0]+edge[i].w;
if(t>dp[u][0])
{
dp[u][1]=dp[u][0]; ///原先次短变最短
dp[u][0]=t; ///更新最短
}
else if(t>dp[u][1])
dp[u][1]=t;
}
}
void dfs2(int u,int fa) ///求每一个结点的反向最大距离
{
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v= edge[i].v;
if(fa==v) continue;
if(dp[u][0]==dp[v][0]+edge[i].w)
{
dp[v][2]=max(dp[u][1],dp[u][2])+edge[i].w;
}
else
{
dp[v][2]=max(dp[u][0],dp[u][2])+edge[i].w;
}
dfs2(v,u);
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
tot=0;
for(int v=2;v<=n;v++)
{
int u,w;
scanf("%d%d",&u,&w);
add(u,v,w);
}
dfs1(1,-1);
dfs2(1,-1);
for(int i=1;i<=n;i++)
printf("%d\n",max(dp[i][0],dp[i][2]));
}
return 0;
}
两次bfs
//树的直径模板
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
using namespace std;
const int maxn=50000+5;
const int maxm=100000+5;
#define ll long long
//有向边
struct Edge
{
Edge(){}
Edge(int to,ll cost,int next):to(to),cost(cost),next(next){}
int to; //边尾部
ll cost; //边距离
int next; //指向下条边
}edges[maxm*2];
int cnt=0; //边总数
int head[maxn];//头结点
//添加两条有向边
void AddEdge(int u,int v,ll cost)
{
edges[cnt]=Edge(v,cost,head[u]);
head[u]=cnt++;
edges[cnt]=Edge(u,cost,head[v]);
head[v]=cnt++;
}
//距离
ll dist[maxn*2];
//BFS返回从s出发能到达的最远点编号
int BFS(int s)
{
int max_dist=0;
int id=s;
queue<int> Q;
memset(dist,-1,sizeof(dist));
dist[s]=0;
Q.push(s);
while(!Q.empty())
{
int u=Q.front();
Q.pop();
if(dist[u]>max_dist)
max_dist=dist[id=u];
for(int i=head[u]; i!=-1; i=edges[i].next)
{
Edge &e=edges[i];
if(dist[e.to]==-1)
{
dist[e.to]=dist[u]+e.cost;
Q.push(e.to);
}
}
}
return id;
}
int main()
{
int n,m;
int t;
cin>>t;
int kase=0;
while(t--)
{
kase++;
scanf("%d",&n);
cnt=0;
memset(head,-1,sizeof(head));
int u,v;
ll cost;
for(int i=1;i<=n-1;i++)
{
scanf("%d%d%lld",&u,&v,&cost);
u++;v++;
AddEdge(u,v,cost);
}
printf("Case %d:",kase);
printf(" %lld\n",dist[BFS(BFS(u))]);
}
return 0;
}
【树形背包】
对于每个节点我们要记录两个值:
f1 [ i ] 表示以 i 为根的子树中,i 到叶子结点距离的最大值
f2 [ i ] 表示以 i 为根的子树中,i 到叶子结点距离的次大值
对于一个节点,它到叶子结点距离的最大值和次大致所经过的路径肯定是不一样的
若j是i的儿子,那么(下面的 w [ i ][ j ] 表示 i 到 j 的路径长度):
若 f1 [ i ] < f1 [ j ] + w [ i ][ j ],f2 [ i ] = f1 [ i ],f1 [ i ] = f1 [ j ] + w [ i ][ j ];
否则,若 f2 [ i ] < f1 [ j ] + w [ i ][ j ],f2 [ i ] = f1 [ j ] + w [ i ][ j ];
理解:这样做就是,先看能否更新最大值,若能,它的次大值就是原先的最大值,再更新它的最大值;若不能,就看能不能更新次大值,若能,就更新,不能就不管它
这样的话,最后的答案 answer = max { f1 [ i ] + f2 [ i ] }
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,t,ans;
int f1[N],f2[N];
int first[N],v[N],w[N],nxt[N];
void add(int x,int y,int z)
{
t++;
nxt[t]=first[x];
first[x]=t;
v[t]=y;
w[t]=z;
}
void dp(int x,int father)
{
int i,j;
for(i=first[x]; i; i=nxt[i])
{
j=v[i];
if(j==father)
continue;
dp(j,x);
if(f1[x]<f1[j]+w[i])
{
f2[x]=f1[x];
f1[x]=f1[j]+w[i];
}
else if(f2[x]<f1[j]+w[i])
f2[x]=f1[j]+w[i];
ans=max(ans,f1[x]+f2[x]);
}
}
int main()
{
int x,y,z,i=1;
while(scanf("%d%d%d",&x,&y,&z)!=-1)
{
add(x,y,z);
add(y,x,z);
i++;
//if(i>5) break;
}
dp(1,0);
printf("%d",ans);
return 0;
}