树的直径、重心、中心
树的直径
树的直径,是指树上最长的一条链。
求树的直径有两种方法
:表示到达子树中叶子节点的最长链,表示到达子树中叶子节点的次长链,两条链不能有交集,只需要对每个节点做以下更新同时维护最大值最小值即可
void dfs1(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs1(ev,u);
if(d1[ev]+e[i].w>d1[u])
d2[u]=d1[u],d1[u]=d1[ev]+e[i].w;
else if(d1[ev]+e[i].w>d2[u])
d2[u]=d1[ev]+e[i].w,c2[u]=ev;
}
}
这样维护保证了不会出现交集,不用更新是因为我们找的是最大值,如果不能跟新更不能更新,如果前者可以更新再用更新也没必要了
最后如此统计答案即可
ans=max(d1[i]+d2[i],ans);(1<=i<=n)
两遍
具体流程:先随意指定一个节点(记为)作为起点搜索到距离起点最远的点,记这个点,从开始搜索到距离最远的点,因为树上路径唯一,就是直径
证明:
假设在直径上,因为第一次搜索是找的最远节点,它一定是叶子节点,所以它一定是直径的一端,从找最远距离一定是直径。
所以我们只需要证明一定在直径上即可,考虑反证法,假设不在直径上,而直径真实的端点是
分两种情况考虑:
在直径上,对于这种情况,因为第一次找的是最远距离,也就是说,根据定义,将直径端点换成显然可以使直径更长,这与“直径是最长链”相违背
不在直径上
可以将直径的段换成更优
这与“直径是最长链”相违背
综上,我们所说的方法是正确的
树的重心
概念:以树的重心为整棵树的根时,它的最大子树最小(也就是删除该点后最大联通块最小)
用表示以为根的子树的总大小(包括根)
用表示以的最大子树的大小
随便指定一个点一下顺便维护上面两个值。
就是删去后上方联通块大小,只需要跟取个,就是以该节点为根时最大子树的大小,然后更新答案即可
ans=INF;
void dfs(int u,int fa)
{
siz[u]=1,mson[u]=0;//注意初始化为0,因为有可能没有子树
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs(ev,u);
siz[u]+=siz[ev];
mson[u]=max(mson[u],siz[ev]);
}
ans=min(ans,max(n-siz[u],mson[u]));
}
树的中心
概念:以树的中心为整棵树的根时,从该根到每个叶子节点的最长路径最短
也有两种方法
树形
我们需要维护每个点到所有叶子节点的最长距离
前面已经知道了怎么维护每个节点到它的子树中的叶子节点的最长距离和次长距离,考虑怎么维护这个点向上的最远距离
表示从哪个点更新,表示从哪个点更新,用表示向上的最远距离。
再用一开始指定的点做一次,这次是从根到叶子节点状态转移。
对于每一个点,假设它的父亲的最长链,也就是不是从它更新来的,那么
如果的父亲的最长链是从它更新来的,那次长链一定不是从它更新来的,可以看看前面的定义,两条链没有交集,所以
最后这样更新答案
ans=min(ans,max(up[i],d1[i]));
直接上完整代码吧
#include<cstdio>
#include<iostream>
#define re register
#define maxn 100010
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct Edge{
int v,w,nxt;
}e[maxn<<2];
int ans,x,y,pos,z;
int n,head[maxn],cnt,d1[maxn],up[maxn],d2[maxn],c1[maxn],c2[maxn];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs1(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs1(ev,u);
if(d1[ev]+e[i].w>d1[u])
d2[u]=d1[u],c2[u]=c1[u],d1[u]=d1[ev]+e[i].w,c1[u]=ev;
else if(d1[ev]+e[i].w>d2[u])
d2[u]=d1[ev]+e[i].w,c2[u]=ev;
}
}
void dfs2(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
if(c1[u]!=ev) up[ev]=max(d1[u],up[u])+e[i].w;//不是从它更新来的
else up[ev]=max(d2[u],up[u])+e[i].w;
dfs2(ev,u);
}
}
int main()
{
n=read();
for(re int i=1;i<n;++i)
{
x=read(),y=read(),z=read();
add(x,y,z),add(y,x,z);
}
dfs1(1,0);
dfs2(1,0);
ans=0x3f3f3f3f;
for(re int i=1;i<=n;++i)
{
if(max(up[i],d1[i])<ans) ans=max(up[i],d1[i]),pos=i;
}
printf("%d %d",pos,ans);//pos表示中心位置
return 0;
}
简单
树的中心一定在树的直径上,且趋于中点
这个是比较显然的,如果不在直径上,它的最远距离只会更远
因此我们在找出直径的同时,对于直径的两个端点,分别求到每个点的距离
最后对于每个点更新即可
ans=min(ans,max(d1[i],d2[i]));
完整代码
#include<cstdio>
#include<iostream>
#define re register
#define maxn 100010
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct Edge{
int v,w,nxt;
}e[maxn<<2];
int x,y,z;
int pos1,pos2,d[maxn],d1[maxn],d2[maxn];
int n,tmp1,tmp2,tmp3,ans,pos,cnt,head[maxn];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs1(int u,int fa,int dis)
{
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev==fa) continue;
dfs1(ev,u,dis+e[i].w);
}
d[u]=dis;
if(dis>tmp2) tmp2=dis,tmp1=u;
}
int main()
{
n=read();
for(re int i=1;i<n;++i)
{
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
dfs1(1,0,0);
pos1=tmp1;
tmp2=0,tmp1=0;
dfs1(pos1,0,0);
pos2=tmp1;
tmp2=0,tmp1=0;
//找到直径了
for(re int i=1;i<=n;++i) d1[i]=d[i];
dfs1(pos2,0,0);
for(re int i=1;i<=n;++i) d2[i]=d[i];
ans=0x3f3f3f3f;
for(re int i=1;i<=n;++i)
{
if(ans>max(d1[i],d2[i]))
ans=max(d1[i],d2[i]),pos=i;
}
printf("%d %d",pos,ans);
return 0;
}
最后附一个树的数据生成器
#include<cstdlib>
#include<iostream>
#include<ctime>
#include<cstdio>
#include<algorithm>
#define re register
using namespace std;
int n,q,qx,qy,w;
int x[10010],y[10010],z[10010];
int a[10010],fa[10010];
int cnt2,cnt;
struct Edge{
int u,v,w;
}e[10010];
int find(int x)
{
return fa[x]==x?x:find(fa[x]);
}
int flag;
int main()
{
srand(time(0));
n=rand()%10+1;
printf("%d\n",n);
for(re int i=1;i<=n;++i)
{
for(re int j=i+1;j<=n;++j)
{
x[++cnt]=i;
y[cnt]=j;
z[cnt]=rand()%100+1;
}
}
for(re int i=1;i<=cnt;++i) a[i]=i,fa[i]=i;
random_shuffle(a+1,a+cnt+1);
for(re int i=1;i<=cnt;++i)
{
int pos=a[i];
int eu=find(x[pos]),ev=find(y[pos]);
if(eu==ev) continue;
fa[ev]=eu;
e[++cnt2].u=x[pos],e[cnt2].v=y[pos],e[cnt2].w=z[pos];
if(cnt2==n-1) break;
}
for(re int i=1;i<=cnt2;++i)
{
printf("%d %d %d\n",e[i].u,e[i].v,e[i].w);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!