题意:求一个
树的直径模板题。
性质
1.直径两端点一定是叶子节点。
2.距任意点最远点一定是直径的端点,据所有点最大值最小的点一定是直径的中点。
3.两棵树相连,新直径的两端点一定是原四个端点中的两个。
4.两棵树相连,设
5.一棵树上接一个叶子结点,直径最多改变一个端点。
6.若一棵树存在多条直径,多条直径交于一点,且交点是直径的严格中点(中点可能在某条边内)。
方法
树形 DP
用
设
如果我们每次遍历时都遍历
code:
void dp(int u,int fa)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa)
continue;
dp(v,u);
ans=max(ans,d[u]+d[v]+e[i].num);
d[u]=max(d[u],d[v]+e[i].num);
}
}
优点:树形 DP 方便,代码量小,且效率较高,支持负权边。
缺点:只能求长度,不能记录路径和两端点。
两次 DFS (BFS)
由树的直径的最长性可知,我们可以现在树中随便找一个节点,然后
void dfs(int u,int fa)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa)
continue;
dis[v]=dis[u]+e[i].num;
if(dis[v]>ans)
{
ans=dis[v];//dfs过程中不断更新直径最大值
rt=v;
}
dfs(v,u);
}
return ;
}
int main()
{
//...省略输入,建图等过程
dfs(1,0);//从一个端点出发
ans=0;
int p=rt;//p为直径一端点
memset(dis,0,sizeof(dis));
dfs(p,0);//再次dfs
printf("%d",dis[rt]);//直径长度
}
优点:多变,可记录端点和路径。
缺点:不支持负权边,时间复杂度较高。
这道题的代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m;
int dis[501010],ans,rt;
int head[501010],cnt;
struct node
{
int next;
int to;
int num;
}e[1010101];
void add(int from,int to,int num)
{
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].num=num;
head[from]=cnt;
}
void dfs(int u,int fa)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa)
continue;
dis[v]=dis[u]+e[i].num;
if(dis[v]>ans)
{
ans=dis[v];
rt=v;
}
dfs(v,u);
}
return ;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b,c;
char s;
scanf("%d%d%d %c",&a,&b,&c,&s);
add(a,b,c);
add(b,a,c);
}
dfs(1,0);
ans=0;
int p=rt;
memset(dis,0,sizeof(dis));
dfs(p,0);
printf("%d",dis[rt]);
return 0;
}