树的重心
- 对于一颗无根树而言,当一个节点被选为根节点,他底下每个子结点的子树的大小(字数包含的节点数目)最大值最小的那个节点被称为树的重心。
- 一棵树可能有1或2个重心。
- 树的重心有如下性质
当重心为根节点时,它底下每个子树的大小不大于整棵树大小的一半。
重心到其他所有节点的距离和(两个节点的距离指的是连接他们的路径的长度)最小。
最小距离和1
给你一棵 n 个节点的树(节点的编号为 1 到 n),现在我们想从树中选出一个节点,使得这个节点到其它所有节点的距离之和最小,请问距离和最小是多少?
输入格式
第一行一个整数 n表示节点数。接下来 n−1行,每行两个整数 x,y 表示 x 号节点和 y号节点之间有一条边。
输入保证是一棵树。
输出格式
输出一行一个数,表示最小距离和。
#include<bits/stdc++.h>
using namespace std;
int n, cnt, ans, dist[100001], f[100001], pre[100001];
vector<int>edges[100001];
inline void dfs(int x){
for(auto y : edges[x])
if(y != pre[x]){
pre[y] =x;
dist[y] = dist[x] + 1;
dfs(y);
}
}
inline void solve(int x){
++cnt;
for(auto y : edges[x])
if(y != pre[x]){
pre[y] = x;
solve(y);
}
}
int main(){
scanf("%d", &n);
//注意树只有n-1条边
for(int i = 1; i < n; i++){
int x, y;
scanf("%d%d", &x, &y);
edges[x].push_back(y);
edges[y].push_back(x);
}
for(int i = 1; i <= n; i++){
f[i] = 0;
memset(pre, 0, sizeof(pre));
for(auto y : edges[i]){
cnt = 0;
pre[y] = i;
solve(y);
f[i] = max(f[i], cnt);
}
}
int idx = 0, v = 1 << 30;
for(int i = 1; i <= n; i++)
if(f[i] < v){
v = f[i];
idx = i;
}
memset(dist, 0, sizeof(dist));
memset(pre, 0, sizeof(pre));
pre[idx] = -1;
dfs(idx);
ans = 0;
for(int i = 1; i <= n; i++)
ans += dist[i];
printf("%d\n", ans);
}
最小距离和2
#include<bits/stdc++.h>
using namespace std;
int n, cnt, ans, dist[100001], f[100001], pre[100001], s[100001];
vector<int>edges[100001];
inline void dfs(int x){
for(auto y : edges[x])
if(y != pre[x]){
pre[y] =x;
dist[y] = dist[x] + 1;
dfs(y);
}
}
inline void solve(int x){
s[x] = 1;
for(auto y : edges[x])
if(y != pre[x]){
pre[y] = x;
solve(y);
s[x] += s[y];
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i < n; i++){
int x, y;
scanf("%d%d", &x, &y);
edges[x].push_back(y);
edges[y].push_back(x);
}
memset(pre, 0, sizeof(pre));
pre[1] = -1;
solve(1);
int idx = 0, v = 1 << 30;
for(int i = 1; i <= n ;i++ ){
int f = 0;
for(auto y : edges[i]){
if(y != pre[i])
f = max(f, s[y]);
else
f = max(f, n - s[i]);
}
if(f < v){
v = f;
idx = i;
}
}
memset(pre, 0, sizeof(pre));
memset(dist, 0, sizeof(dist));
pre[idx] = -1;
dfs(idx);
long long ans;
for(int i = 1; i <= n; i++)
ans += dist[i];
printf("%lld\n", ans);
//输出longlong
}
树的最近公共祖先
- 两个节点的最近公共祖先(LCA)问题是指:对于两个节点u,v,找到一个树深度最大的x,x是u,v的祖先。多个节点的LCA问题可以转换成多次求两个节点LCA的形式。
- 一般树求LCA(u, v)的方法
先计算出两个节点的深度;
将u、v调整到同一深度;
两个节点一起逐级往上跳,直到两节点相等。
例题: LCA(1)
给你一棵 n 个节点的以 1 号节点为根的树,节点的编号为 1 到 n。现在有 m 组询问,对于每组询问 u,v,请求出 u 号节点和 v号节点的最近公共祖先。
输入格式
第一行一个整数 n表示节点数。
接下来 n−1行,每行两个整数 x,y 表示 x 号节点和 y号节点之间有一条边。输入保证是一棵树。
接下来一行一个整数 m表示询问数。
接下来 m行,每行两个整数 u,v表示一组询问。
输出格式
输出共 m行,对于每组询问,输出一行一个整数表示对应的两个点的最近公共祖先的编号。
数据规模
对于所有数据,保证 1≤n,m≤1000,1≤u,v≤n。
#include<bits/stdc++.h>
using namespace std;
int n, m , dist[1001], fa[1001];
vector<int>edges[1001];
inline void dfs(int x){
for(auto y : edges[x]){
dist[y] = dist[x] + 1;
dfs(y);
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i < n; i++){
int x, y;
scanf("%d%d", &x, &y);
edges[x].push_back(y);
fa[y] = x;
}
memset(dist, 0, sizeof(dist));
dfs(1);
scanf("%d", &m);
for(int i = 1; i <= m; i++){
int x, y;
scanf("%d%d", &x, &y);
if(dist[x] < dist[y])
swap(x, y);
int z = dist [x] - dist[y];
for(int j = 1; j <= z; j++)
x = fa[x];
while(x != y){
x = fa[x];
y = fa[y];
}
printf("%d\n", x);
}
}
例题2:LCA(2)
数据规模
对于所有数据,保证 1≤n,m≤100000,1≤u,v≤n。
#include<bits/stdc++.h>
using namespace std;
int n, m, fa[100001][21], dist[100001];
vector<int>edges[100001];
inline void dfs(int x){
for(auto y : edges[x]){
dist[y] = dist[x] + 1;
dfs(y);
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i < n; i++){
int x, y;
scanf("%d%d", &x, &y);
edges[x].push_back(y);
fa[y][0] = x;
}
for(int i = 1; i <= 20; i++)
for(int j = 1; j<= n; j++)
if(fa[j][i-1])
fa[j][i] = fa[fa[j][i-1]][i-1];
memset(dist, 0, sizeof(dist));
dfs(1);
scanf("%d", &m);
for(int i = 1; i <= m; i++){
int x, y;
scanf("%d%d", &x, &y);
if(dist[x] < dist[y])
swap(x, y);
int z = dist[x] - dist[y];
for(int j = 0; j <= 20 &&z; j++, z/=2)
if(z & 1)
x = fa[x][j];
if(x == y){
printf("%d\n", x);
continue;
}
for(int j = 20; j >= 0; j--)
if(fa[x][j] != fa[y][j])
x = fa[x][j], y = fa[y][j];
printf("%d\n", fa[x][0]);
}
}