【概述】
樹的中心問題是指:當給出 n 個結點與 n-1 條邊後,要選定一個點作爲整棵樹的根結點,使得從該點到每個葉結點的最長路徑最短。
樹的中心問題主要有兩種方法:DFS/BFS 進行搜索、樹形 DP 進行狀態轉移
【DFS】
根據樹的中心問題的描述,顯然可以知道,樹的中心一定在樹的直徑上,而且趨於終點,否則它的最遠距離只會更遠。
因此,我們在利用 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】
利用樹形 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;
}