29.直径与重心

一、树的直径

1.定义

树上任意两节点之间最长的简单路径即为树的直径。

简单路径:一条路径上没有重复的边即为简单路径。

显然,一棵树可能会有多条直径,它们的长度相等。通常我们有两种求解树的直径的方式,分别是两次 DFS 或者树形 DP 的方法,都可以在 O ( n ) O(n) O(n) 的时间求出树的直径。下面分别进行介绍。

2.两次 DFS 求树的直径

(1)算法流程

首先从任意节点 r o o t root root 开始进行第一次 DFS,到达距离其最远的节点,记为 s t a r t start start,然后再从 s t a r t start start 开始做第二次 DFS,到达距离 s t a r t start start 最远的节点,记为 s t a r t start start,则 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina) 即为树的直径。

显然,如果第一次 DFS 到达的节点 s t a r t start start 是直径的一端,那么第二次 DFS 到达的节点 f i n a fina fina 一定是直径的一端。我们只需证明在任意情况下, s t a r t start start 必为直径的一端。

定理:在一棵树上,从任意节点 r o o t root root 开始进行一次 DFS,到达的距离其最远的节点 s t a r t start start 必为直径的一端。

(2)证明

使用反证法。记出发节点为 r o o t root root。设真实的直径是 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina),而从 r o o t root root 进行的第一次 D F S DFS DFS 到达的距离其最远的节点 x x x 不为 s t a r t start start 或 f i n a fina fina。共分三种情况:

  • 若 r o o t root root 在 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina) 上 :

有 δ ( r o o t , x ) > δ ( r o o t , f i n a ) → δ ( s t a r t , x ) > δ ( s t a r t , f i n a ) \delta(root,x)>\delta(root,fina)\rightarrow\delta(start,x)>\delta(start,fina) δ(root,x)>δ(root,fina)→δ(start,x)>δ(start,fina) ,与 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina) 为树上任意两节点之间最长的简单路径矛盾。

  • 若 r o o t root root 不在 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina)上,且与存在重合路径为 δ ( a , b ) \delta(a,b) δ(a,b):

有 δ ( r o o t , x ) > δ ( r o o t , f i n a ) → δ ( a , x ) > δ ( a , f i n a ) → δ ( s t a r t , x ) > δ ( s t a r t , f i n a ) \delta(root,x)>\delta(root,fina)\rightarrow\delta(a,x)>\delta(a,fina)\rightarrow\delta(start,x)>\delta(start,fina) δ(root,x)>δ(root,fina)→δ(a,x)>δ(a,fina)→δ(start,x)>δ(start,fina) ,与 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina) 为树上任意两节点之间最长的简单路径矛盾。

  • 若 r o o t root root 不在 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina)上,且不存在重合路径, δ ( r o o t , x ) \delta(root,x) δ(root,x) 上有一点 x x x 与 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina)上有一点 x ′ x' x′ 直接相连:

有 δ ( r o o t , x ) > δ ( r o o t , f i n a ) ⟹ δ ( x ′ , x ) > δ ( x ′ , f i n a ) ⟹ δ ( x , f i n a ) > δ ( x , f i n a ) ⟹ δ ( s t a r t , x ) > δ ( s t a r t , f i n a ) \delta(root,x)>\delta(root,fina)\Longrightarrow \delta(x',x)>\delta(x',fina)\Longrightarrow\delta(x,fina) > \delta(x,fina)\Longrightarrow\delta(start,x)>\delta(start,fina) δ(root,x)>δ(root,fina)⟹δ(x′,x)>δ(x′,fina)⟹δ(x,fina)>δ(x,fina)⟹δ(start,x)>δ(start,fina),与 δ ( s t a r t , f i n a ) \delta(start,fina) δ(start,fina) 为树上任意两节点之间最长的简单路径矛盾。

综上,三种情况下假设均会产生矛盾,故原定理得证。

(3)模板代码

cpp 复制代码
void dfs(ll u,ll pre)
{
	if(d[u]>d[pos])
		pos=u;
	for(ll i=p[u];i!=-1;i=e[i].next)
	{
		ll v=e[i].v,w=e[i].w;
		if(v!=pre)
		{
			d[v]=d[u]+w;
			dfs(v,u);
		}
	}
}
int main()
{
	memset(p,-1,sizeof(p));
    scanf("%lld",&n);
    for(ll i=1;i<n;i++)
	{
    	ll u,v,w;
    	scanf("%lld%lld%lld",&u,&v,&w);
    	insert(u,v,w);
    	insert(v,u,w);
	}
	dfs(1,0);
	memset(d,0,sizeof(d));
	start=pos;pos=0;
	dfs(start,0);
	fina=pos;
	printf("%lld\n",d[fina]);
    
	return 0;
}
(4)缺陷

从证明不难看出,这个方法无法处理负边权的情况。如果出现负边权,则无法采用两次 D F S DFS DFS 的方法,而只能采用下面所讲到的树形 D P DP DP 的方法。

2.树形 D P DP DP

(1)算法流程

我们记录当 1 1 1 为树的根时,每个节点作为子树的根向下,所能延伸的最长路径长度 d 1 d_1 d1与次长路径(与最长路径无公共边)长度 d 2 d_2 d2 ,那么直径就是对于每一个点,该点 d 1 + d 2 d_1+d_2 d1+d2 能取到的值中的最大值。

树形 D P DP DP 可以在存在负权边的情况下求解出树的直径。

(2)模板代码

cpp 复制代码
void dfs(ll u,ll pre)
{
	for(ll i=p[u];i!=-1;i=e[i].next)
	{
		ll v=e[i].v,w=e[i].w;
		if(v!=pre)
		{
			dfs(v,u);
			ll temp=d1[v]+w;
			if(temp>d1[u])
			{
				d2[u]=d1[u];
				d1[u]=temp;
			}
			else if(temp>d2[u])
				d2[u]=temp;
		}
	}
	d=max(d,d1[u]+d2[u]);
}
int main()
{
	freopen("test.txt","r",stdin);
	memset(p,-1,sizeof(p));
    ll u,v,w;
    while(~scanf("%lld%lld%lld",&u,&v,&w))
	{
    	insert(u,v,w);
    	insert(v,u,w);
	}
	dfs(1,0);
	printf("%lld\n",d);
    
	return 0;
}

二、树的重心

1.定义

对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。

子树:指无根树的子树,即包括向上的那棵子树,并且不包括整棵树自身。

2.性质

  • 树的重心如果不唯一,则至多有两个,且这两个重心相邻。
  • 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
  • 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
  • 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
  • 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

3.算法流程

在 D F S DFS DFS 中计算每个子树的大小,记录向下的子树的最大大小,利用总点数 - 当前子树的大小得到向上的子树的大小,然后就可以依据定义找到重心了。

4.模板代码

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm> 
#include <cstring>

using namespace std;
typedef long long ll;
const ll maxn=50010;
struct node
{
    ll v;
    ll next;
}e[2*maxn];
ll n,t=0,p[maxn];
ll minnode=-1,Min=1e18;
void insert(ll u,ll v)
{
    e[t].v=v;
    e[t].next=p[u];
    p[u]=t++;
}
ll dfs(ll u,ll pre)
{
    ll sz=0,Max=0;
    for(ll i=p[u];i!=-1;i=e[i].next)
    {
    	ll v=e[i].v;
        if(v!=pre)
        {
            ll son=dfs(v,u);
            sz+=son;
            Max=max(Max,son);
        }
    }
    sz++;
    Max=max(Max,n-sz);
    if(Max<Min || (Max==Min && u<minnode))
    {
        Min=Max;
        minnode=u;
    }
    return sz;
}
ll d[maxn],sum=0;
void getans(ll u,ll pre)
{
    for(ll i=p[u];i!=-1;i=e[i].next)
    {
    	ll v=e[i].v;
        if(v!=pre)
        {
        	d[v]=d[u]+1;
        	sum+=d[v];
            getans(v,u);
        }
    }
}
int main()
{
    memset(p,-1,sizeof(p));
    scanf("%lld",&n);
    for(ll i=1;i<=n-1;i++)
    {
        ll a,b;
        scanf("%lld%lld",&a,&b);
        insert(a,b);
        insert(b,a);
    }
    ll temp=dfs(1,0);
    d[minnode]=0;
    getans(minnode,0);
    printf("%lld %lld",minnode,sum);
    
    return 0;
}

三、作业

POJ 2631

P1395 会议

相关推荐
程序员-King.7 分钟前
2、桥接模式
c++·桥接模式
chnming198711 分钟前
STL关联式容器之map
开发语言·c++
VertexGeek14 分钟前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
石小石Orz15 分钟前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
程序伍六七24 分钟前
day16
开发语言·c++
小陈phd42 分钟前
Vscode LinuxC++环境配置
linux·c++·vscode
火山口车神丶1 小时前
某车企ASW面试笔试题
c++·matlab
jiao_mrswang1 小时前
leetcode-18-四数之和
算法·leetcode·职场和发展
qystca1 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱1 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea