树的直径
树上任意两节点之间最长的简单路径即为树的直径。
即树上最长的链
显然树可以有多条直径
SPOJ PT07Z
模版
树的直径两种求法,时间复杂度均为\(O(n)\)
常用的是两遍dfs
第一遍dfs从任一点开始,找到可以到达的最远点,这个最远点就是直径的一个端点,第二遍dfs再从这一端点出发,再一次去找可以到达的最远点,这个点就是直径的另一端点
void dfs(int i,int fa){
f[i]=fa,maxdep[i]=dis[i];
if(dis[i]>maxn){
maxn=dis[i];
id=i;
}
for(int j=head[i];~j;j=e[j].next){
int v=e[j].v;
if(v==fa||flg[v])continue;
dis[v]=dis[i]+1;
dfs(v,i);
maxdep[i]=max(maxdep[i],maxdep[v]);
}
}
void gettree(){
maxn=-1;
dfs(1,0);
int beginpoint=id;
maxn=-1,dis[bp]=0;
dfs(bp,0);
int endpoint=id;
}
缺点是不可用于含有负权的树
第二种方法是dp
维护每个点向下延伸的最长距离\(d1[i]\)和\(d2[i]\),树的直径的长度即为\(max\{d1[i]+d2[i]\}\)
void dfs(int i,int fa){
for(int j=head[i];~j;j=e[j].next){
int v=e[j].v;
if(v!=fa){
dfs(v,i);
if(d1[v]+1>d1[i]){
d2[i]=d1[i];
d1[i]=d1[v]+1;
}else if(d1[v]+1>d2[i]){
d2[i]=d1[v]+1;
}
}
}
ans=max(ans,d1[i]+d2[i]);
}
可以用于含有负权的树
P1099&P2491
在直径上找到一段不超过限制长度的路径\(F\),使得\(F\)外节点到\(F\)的最大距离(偏心距)最小
比较暴力的做法是,先求出直径,然后分别从直径上的每个点开始,沿直径延伸到达最远的点,这样确定了路径\(F\),然后dfs统计答案。确定路径\(F\)时可以使用双指针法,这样时间复杂度为\(O(n^2)\)。实际上,偏心距只能是直径的两端点到路径\(F\)两端的距离较大者,设为\(x\),或者直径上的一点不经过直径上的点所到达的最远距离,设为\(y\)。前者显然,所以计算偏心距时,对于双指针找出的每一段路径\(F\),偏心距取所有\(x\)的距离的最小值,其他情况必然更劣,然后dfs与所有\(y\)取最大值。
可能疑惑:是否可能存在一条\(y\),它在直径上的端点不在所选取的\(F\)上,导致所得的偏心距偏小,实际上是不可能的,这样违背了直径最长的性质。最终的时间复杂度是\(O(n)\)
void dfs(int i,int fa){
f[i]=fa;
if(d[i]>maxn){
maxn=d[i];
id=i;
}
for(int j=head[i];~j;j=e[j].next){
int v=e[j].v,w=e[j].w;
if(v!=fa&&!vis[v]){
d[v]=d[i]+w;
dfs(v,i);
}
}
}
int main(){
memset(head,-1,sizeof(head));
n=read(),s=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
addedge(u,v,w);addedge(v,u,w);
}
int bp,ep;
dfs(1,0);
bp=id;
d[bp]=0;maxn=0;
dfs(bp,0);
ep=id;
for(int i=ep,j=ep;i;i=f[i]){//双指针
while(d[j]-d[i]>s)j=f[j];
ans=min(ans,max(d[i],d[ep]-d[j]));
}
for(int i=ep;i;i=f[i]){//这个for不要和下面的for写到一起
vis[i]=1;
}
for(int i=ep;i;i=f[i]){
d[i]=0;
dfs(i,f[i]);
}
for(int i=1;i<=n;i++){
ans=max(ans,d[i]);
}
cout<<ans<<"\n";
return 0;
}
P4408
可以知道,其中一条路径是树的直径,那么考虑另一条路径的选取,设该路径为\(F\),可以知道\(F\)的一个端点是直径的一个端点,于是求出所有点到直径两个端点的距离\(d1\)和\(d2\),根据题目描述,各个点所能的贡献最长距离\(d[i]=min\{d1[i],d2[i]\}\),路径\(F\)的长度为\(max\{d[i]\}\)
long long
P5536
找一个大小为\(k\)的联通块,使联通块之外点到联通块的最大距离最小
与树网的核不同,\(k\)可能大于直径
易知树的直径的中点是必选的,因为直径中点到所有叶子的最大距离最小(否则违背了直径最长性质),如果不选一定更劣。由此,从直径中点dfs计算每个点的深度\(dep[i]\)和可以到达的最大距离\(maxdep[i]\),按照\(maxdep[i]-dep[i]\)排序,优先选择较大者
void dfs(int i,int fa){
f[i]=fa,maxdep[i]=dis[i];
if(dis[i]>maxn){
maxn=dis[i];
id=i;
}
for(int j=head[i];~j;j=e[j].next){
int v=e[j].v;
if(v==fa||flg[v])continue;
dis[v]=dis[i]+1;
dfs(v,i);
maxdep[i]=max(maxdep[i],maxdep[v]);
}
}
int main(){
memset(head,-1,sizeof(head));
n=read(),k=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
addedge(u,v);addedge(v,u);
}
maxn=-1;
dfs(1,0);
int bp=id;
maxn=-1,dis[bp]=0;
dfs(bp,0);
int ep=id;
int mid=0;
for(int j=0,i=ep;j<=dis[ep]/2;j++,i=f[i])
mid=i;
dis[mid]=0;
dfs(mid,0);
for(int i=1;i<=n;i++)
maxdep[i]=maxdep[i]-dis[i];
sort(maxdep+1,maxdep+n+1);
for(int i=1;i<=n-k;i++)
ans=max(ans,maxdep[i]);
cout<<ans+1<<"\n";
return 0;
}
标签:int,dfs,fa,直径,d1,dis
From: https://www.cnblogs.com/wertyuio1/p/18372579