树的直径
定义
给定一棵树,树中每条边都有一个权值,树中两点之间的距离定义为连接两点的路径边权之和。树中最远的两个节点之间的距离被称为树的直径,连接这两点的路径被称为树的最长链。后者通常也可称为直径,即直径是一个数值概念,也可代指一条路径
性质
- 直径两端点一定是两个叶子节点
- 距离任意点最远的点一定是直径的一个端点,这个基于贪心求直径方法的正确性可以得出
- 对于两棵树,如果第一棵树直径两端点为
(u,v)
,第二棵树直径两端点为(x,y)
,用条边将两棵树连接,那么新树的直径一定是u,v,x,y
中的两个点 - 对于一棵树,如果在一个点的上接一个叶子节点,那么最多会改变直径的一个端点
- 若一棵树存在多条直径,那么这些直径交于一点且交点是这些直径的中点
算法
求树的直径有两种算法,一种是跑两边搜索,另一种是用树形DP去求(可惜我不会qwq)
这里我们来介绍用两遍搜索求树的直径的方法.首先,我们从任意一个点出发,找出和ta的距离最远的点u
;然后再从u
出发,搜索树上距离u
最远的节点v
.u
和v
之间的路径就是树的直径.
证明:OI不需要证明……代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
using namespace std;
int read()
{
register int x = 0,f = 1;register char ch;
ch = getchar();
while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
return x * f;
}
struct edge{
int next,to,v;
}e[100005];
int n,m,x,y,z,cnt,maxx,j,d[100005],vis[100005],last[100005];
void add(int x, int y, int z)
{
e[++cnt].to = y;
e[cnt].v = z;
e[cnt].next = d[x];
d[x] = cnt;
}
void dfs(int k, int l)
{
if(l > maxx)
{
maxx = l;
j = k;
}
for(re int i = d[k]; i; i = e[i].next)
if(!vis[e[i].to])
{
vis[e[i].to] = 1;
last[e[i].to] = k;
dfs(e[i].to,l + e[i].v);
}
}
void work()
{
vis[1] = 1;
dfs(1,0);
memset(vis,0,sizeof(vis));
memset(last,0,sizeof(last));
maxx = 0;
vis[j] = 1;
dfs(j,0);
printf("%d\n",maxx);
while(j != 0)
{
printf("%d ",j);
j = last[j];
}
}
int main()
{
n = read();
m = read();
for(re int i = 1; i <= m; i++)
{
x = read(); y = read(); z = read();
add(x,y,z);
add(y,x,z);
}
work();
return 0;
}
树的重心
定义
树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个 点后最大连通块(一定是树)的结点数最小。
性质
- 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样。
- 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
- 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
- 一棵树最多有两个重心,且相邻。
算法
和树的最大独立问题类似,先任选一个结点作为根节点,把无根树变成有根树,然后设d(i)表示以i为根的子树的结点的个数。不难发现d(i)=∑d(j)+1,j∈s(i)。s(i)为i结点的所有儿子结点的编号的集合。程序也十分简单:只需要DFS一次,在无根树有根数的同时计算即可,连记忆化都不需要——因为本来就没有重复计算。代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
using namespace std;
int read()
{
register int x = 0,f = 1;register char ch;
ch = getchar();
while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
return x * f;
}
struct edge{
int next,to,v;
}e[100005];
int n,m,x,y,z,cnt,maxx,j,d[100005],vis[100005],last[100005];
void add(int x, int y, int z)
{
e[++cnt].to = y;
e[cnt].v = z;
e[cnt].next = d[x];
d[x] = cnt;
}
void dfs(int k, int l)
{
if(l > maxx)
{
maxx = l;
j = k;
}
for(re int i = d[k]; i; i = e[i].next)
if(!vis[e[i].to])
{
vis[e[i].to] = 1;
last[e[i].to] = k;
dfs(e[i].to,l + e[i].v);
}
}
void work()
{
vis[1] = 1;
dfs(1,0);
memset(vis,0,sizeof(vis));
memset(last,0,sizeof(last));
maxx = 0;
vis[j] = 1;
dfs(j,0);
printf("%d\n",maxx);
while(j != 0)
{
printf("%d ",j);
j = last[j];
}
}
int main()
{
n = read();
m = read();
for(re int i = 1; i <= m; i++)
{
x = read(); y = read(); z = read();
add(x,y,z);
add(y,x,z);
}
work();
return 0;
}