利用叶子枝长公式 limb(x)=min((d(x,i)+d(x,j)-d(i,j))/2),递归删去一个叶子并累加其独有边长,最终得到整棵树的总重量。
OJ: luogu
题目 ID: P1268
难度:普及+/提高
标签:树递归推导思维
日期: 2026-06-20 23:55
题意
给出一棵带非负整数边权树的叶子两两距离矩阵。
矩阵保证合法,也就是说确实存在某棵树满足这些叶子间距离。
现在不要求恢复整棵树,只要求输出这棵树所有边权之和。
思路
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 35;
const int INF = 1e9;
int n;
int dis[MAXN][MAXN];
// brute.cpp:小数据验证版。
// 思路和正式解一致,但直接用最朴素的三重枚举去找每次删掉叶子的枝长。
int solve_bruteforce(int size_now) {
if (size_now == 2) {
return dis[1][2];
}
int x = size_now;
int limb = INF;
for (int i = 1; i <= size_now - 1; i++) {
for (int j = 1; j <= size_now - 1; j++) {
if (i == j) {
continue;
}
int value = (dis[x][i] + dis[x][j] - dis[i][j]) / 2;
limb = min(limb, value);
}
}
return solve_bruteforce(size_now - 1) + limb;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
dis[i][i] = 0;
}
for (int i = 1; i <= n - 1; i++) {
for (int j = i + 1; j <= n; j++) {
cin >> dis[i][j];
dis[j][i] = dis[i][j];
}
}
cout << solve_bruteforce(n) << '\n';
return 0;
}这题最关键的不是构造整棵树,而是发现:
如果当前有一个叶子
这里最小值是在所有
原因是:
- 对任意
、 ,这个式子都不会小于 的真实枝长 - 当
、 分别落在 接入点的两侧时,会恰好取到等号
于是问题就能递归解决:
- 求出当前最后一个叶子的枝长
- 把它加入答案
- 删除这个叶子
- 继续处理剩下的叶子
为什么可以直接删?
因为删掉一个叶子以及它独有的末端边,不会影响其他叶子之间的距离,所以剩余部分还是同类问题。
递归到只剩两个叶子时,树就只剩一条边,答案就是它们之间的距离。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 35;
const int INF = 1e9;
int n;
int dis[MAXN][MAXN];
// 计算当前最后一个叶子 x 连到树上的那条“枝长”。
int get_limb_length(int size_now) {
int x = size_now;
int best = INF;
for (int i = 1; i <= size_now - 1; i++) {
for (int j = i + 1; j <= size_now - 1; j++) {
int value = (dis[x][i] + dis[x][j] - dis[i][j]) / 2;
if (value < best) {
best = value;
}
}
}
return best;
}
// 递归删除最后一个叶子,累加它独有的那条边长。
int solve(int size_now) {
if (size_now == 2) {
return dis[1][2];
}
int limb = get_limb_length(size_now);
return solve(size_now - 1) + limb;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
dis[i][i] = 0;
}
// 输入的是上三角矩阵。
for (int i = 1; i <= n - 1; i++) {
for (int j = i + 1; j <= n; j++) {
cin >> dis[i][j];
dis[j][i] = dis[i][j];
}
}
cout << solve(n) << '\n';
return 0;
}复杂度
每次求一个叶子的枝长要枚举两重叶子对,复杂度是
总时间复杂度:
空间复杂度:
总结
这题的核心不是“把树建出来”,而是抓住叶子独有边长的三点公式。
一旦想到:
- 叶子可以一个个删掉
- 删掉前先用距离矩阵算出它的枝长
总重量就能直接递归算出来。
