树的中心问题是指:当给出 n 个结点与 n-1 条边后,要选定一个点作为整棵树的根结点,使得从该点到每个叶结点的最长路径最短。
树的中心问题主要有两种方法:DFS/BFS 进行搜索、树形 DP 进行状态转移
根据树的中心问题的描述,显然可以知道,树的中心一定在树的直径上,而且趋于终点,否则它的最远距离只会更远。
因此,我们在利用 DFS 寻找树的直径的同时,对于直径的两个端点 st、ed,分别求其到每个点的距离 disSt[i]、disEd[i]
最后,对每个点进行更新,求最小距离 即可
struct Edge {
int to, val;
int next;
Edge(){}
Edge(int to,int val,int next):to(to),val(val),next(next){}
} edge[N];
int n;
int head[N], tot;
int diameter,maxx, id;
int dis[N], disSt[N], disEd[N];
void addEdge(int from, int to, int val) {
edge[++tot].to = to;
edge[tot].val = val;
edge[tot].next = head[from];
head[from] = tot;
}
void dfs(int x, int father) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int y = edge[i].to;
int val = edge[i].val;
if (y == father)
continue;
dis[y] = dis[x] + val;
if(dis[y]>maxx){
maxx = dis[y];
id = y;
}
dfs(y, x);
}
}
void calcDiameter(){
//第一遍dfs
maxx = 0;
id = 1;
dfs(1, 0);
int st = id;
//第二遍dfs
maxx = 0;
dis[st] = 0;
dfs(st, 0);
int ed = id;
diameter = maxx; //树的直径
for (int i = 1; i <= n; i++)
disSt[i] = dis[i];
dis[ed] = 0;
dfs(ed, 0);
for (int i = 1; i <= n; i++)
disEd[i] = dis[i];
}
int main() {
scanf("%d", &n);
memset(head, -1, sizeof(head));
for (int i = 1; i <= n - 1; i++) {
int x, y, val;
scanf("%d%d%d", &x, &y, &val);
addEdge(x, y, val);
addEdge(y, x, val);
}
calcDiameter();
int pos, minn = INF;
for (int i = 1; i <= n; ++i) {
if (minn > max(disSt[i], disEd[i])){
minn = max(disSt[i], disEd[i]);
pos = i;
}
}
printf("%d %d\n", pos, minn);
return 0;
}
利用树形 DP 求解时,我们需要维护每个点 i 到所有叶结点的最长距离 up[i],从而去更新树的中心。
由于采用树形 DP 的方法,在求树的直径时已经知道如何维护每个结点 i 到其子树的叶结点的最长距离 dis1[i] 与次长距离 dis2[i],那么接下来就要考虑如何维护这个点向上的最远距离 up[i]
依旧用 pos1[x] 表示 dis1[x] 在哪个点更新,pos2[x] 表示 dis2[x] 在哪个点更新,再求出树的直径后,再次进行一次 DFS 即可
struct Edge {
int to, val;
int next;
Edge(){}
Edge(int to,int val,int next):to(to),val(val),next(next){}
} edge[N];
int n;
int head[N], tot;
int dis1[N], dis2[N];//分别维护第i个点的最长链、次长链
int pos1[N],pos2[N];//分别维护dis1[i]、dis2[i]从哪个点更新
int up[N];//点i到所有叶结点的最远距离
void addEdge(int from, int to, int val) {
edge[++tot].to = to;
edge[tot].val = val;
edge[tot].next = head[from];
head[from] = tot;
}
void dfs(int x, int father) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int y = edge[i].to;
int val = edge[i].val;
if (y == father)
continue;
dfs(y, x);
if (dis1[y] + val > dis1[x]) {
dis2[x] = dis1[x];
dis1[x] = dis1[y] + val;
pos2[x] = pos1[x];
pos1[x] = y;
}
else if (dis1[y] + val > dis2[x]) {
dis2[x] = dis1[y] + val;
pos2[x] = y;
}
}
}
void dfsCenter(int x, int father) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int y = edge[i].to;
int val = edge[i].val;
if (y == father)
continue;
if (pos1[x] != y)
up[y] = max(dis1[x], up[x]) + val;
else
up[y] = max(dis2[x], up[x]) + val;
dfsCenter(y, x);
}
}
int main() {
scanf("%d", &n);
memset(head, -1, sizeof(head));
for (int i = 1; i <= n - 1; i++) {
int x, y, val;
scanf("%d%d%d", &x, &y, &val);
addEdge(x, y, val);
addEdge(y, x, val);
}
dfs(1, 0);
dfsCenter(1, 0);
int pos, minn = INF;
for (int i = 1; i <= n; i++) {
if (minn > max(up[i], dis1[i])) {
minn = max(up[i], dis1[i]);
pos = i;
}
}
printf("%d %d", pos, minn);
return 0;
}
树的中心问题,最常见的一种变型问题是:给出一棵树 n 个点的点权与 n-1 条边的边权,求树的最小代价的和,定义代价为树中两点距离乘以点的点权
该问题是最常见的,一般数据规模较小,利用 Floyd 算法即可解决。
int n;
int G[N][N], node[N], sum[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) //点权
cin >> node[i];
for (int i = 1; i <= n - 1; i++) { //边权
int x, y, dis;
cin >> x >> y >> dis;
G[x][y] = dis;
G[y][x] = dis;
}
//Floyd记录各点间的距离
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (G[i][j] > G[i][k] + G[k][j])
G[i][j] = G[i][k] + G[k][j];
//枚举求最小代价
int minn = INF;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
sum[i] += G[i][j] * node[j];
if (sum[i] < minn)
minn = sum[i];
}
cout << minn << endl;
return 0;
}