树的直径、重心、中心
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
树的直径、重⼼、中⼼
树的直径
树的直径,是指树上最长的⼀条链。
求树的直径有两种⽅法
1.DP:d1[u]表⽰u到达⼦树中叶⼦节点的最长链,d2[u]表⽰u到达⼦树中叶⼦节点的次长链,两条链不能有交集,只需要对每个节点做以下更新同时维护最⼤值最⼩值即可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;
}
}
这样维护保证了不会出现交集,不⽤d2[ev]更新是因为我们找的是最⼤值,如果d1[ev]不能跟新d2[ev]更不能更新,如果前者可以更新再⽤d2[ev]更新也没必要了
最后如此统计答案即可
ans=max(d1[i]+d2[i],ans);(1<=i<=n)
2.两遍BFS/DFS
具体流程:先随意指定⼀个节点(记为z)作为起点搜索到距离起点最远的点,记这个点x,从x开始搜索到距离x最远的点y,因为树上路径唯⼀,x−>y就是直径
证明:
假设x在直径上,因为第⼀次搜索是找的最远节点,它⼀定是叶⼦节点,所以它⼀定是直径的⼀端,从x找最远距离⼀定是直径。
所以我们只需要证明x⼀定在直径上即可,考虑反证法,假设x不在直径上,⽽直径真实的端点是q
分两种情况考虑:
(1)k在直径上,对于这种情况,因为第⼀次找的是最远距离,也就是说dis(k,x)>dis(k,q),根据定义,将直径端点换成x显然可以使直径更长,这与“直径是最长链”相违背
(2)k不在直径上
∵b+d>d+c+a
∴b>c+a
∴b+c>a
可以将直径的a段换成b+c更优
这与“直径是最长链”相违背
综上,我们所说的⽅法是正确的
树的重⼼
概念:以树的重⼼为整棵树的根时,它的最⼤⼦树最⼩(也就是删除该点后最⼤联通块最⼩)
⽤siz[i]表⽰以i为根的⼦树的总⼤⼩(包括根)
⽤mson[i]表⽰以i的最⼤⼦树的⼤⼩
随便指定⼀个点dfs⼀下顺便维护上⾯两个值。
n−siz[i]就是删去i后上⽅联通块⼤⼩,只需要跟mson[i]取个max,就是以该节点为根时最⼤⼦树的⼤⼩,然后更新答案即可
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]));
}
树的中⼼
概念:以树的中⼼为整棵树的根时,从该根到每个叶⼦节点的最长路径最短
也有两种⽅法
1.树形DP
我们需要维护每个点到所有叶⼦节点的最长距离
前⾯已经知道了怎么维护每个节点到它的⼦树中的叶⼦节点的最长距离和次长距离,考虑怎么维护这个点向上的最远距离
c1[i]表⽰d1[i]从哪个点更新,c2[i]表⽰d2[i]从哪个点更新,⽤up[i]表⽰向上的最远距离。
再⽤⼀开始指定的点做⼀次DFS,这次是从根到叶⼦节点状态转移。
对于每⼀个点,假设它的⽗亲的最长链,也就是d1[fa[u]]不是从它更新来的,那么up[u]=max(up[fa[u]],d1[fa[u]])+dis[fa[u]][u]如果的⽗亲的最长链是从它更新来的,那次长链⼀定不是从它更新来的,可以看看前⾯的定义,两条链没有交集,所以
up[u]=max(up[fa[u]],d2[fa[u]])+dis[fa[u]][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;
}
2.简单DFS/BFS
树的中⼼⼀定在树的直径上,且趋于中点
这个是⽐较显然的,如果不在直径上,它的最远距离只会更远
因此我们在找出直径的同时,对于直径的两个端点pos1,pos2,分别求到每个点的距离d1[i],d2[i]最后对于每个点更新即可
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;
}
Processing math: 100%。