最近在洛谷上遇到一道有趣的图论题目P7807"魔力滋生",题目描述了一种特殊的树结构变换过程:给定一棵初始树T,每个节点u会随机生成x≥k个新节点并与之连接,最终形成扩展树T'。现在的问题是:已知扩展后的树T',如何逆向还原出原始树T?如果存在多种可能的原始树,我们需要找到其中节点数n最大的那个解。
这个问题的难点在于:
根据题目描述,原始树T有一个重要性质:任意节点的度数不超过2。这意味着原始树只能是以下几种结构:
这个性质为我们提供了重要的解题突破口:
cpp复制// 检查节点度数是否合法
for (auto& [u, v] : edge) {
deg[u]++;
deg[v]++;
if (deg[u] > 2 || deg[v] > 2) {
// 不符合原始树性质
}
}
题目中的参数k直接影响解题策略:
当k > 0时:
当k = 0时:
对于k=0的情况,我们需要找到树T'中的最长链。这是因为:
计算树的直径的标准方法是两次BFS:
基于上述分析,算法实现步骤如下:
统计节点度数:
cpp复制vector<int> deg(M);
for (auto& [u, v] : edge) {
u--, v--; // 转为0-based
deg[u]++; deg[v]++;
}
根据k值处理边:
离散化节点编号:
cpp复制CDiscretize dis(ps); // ps为保留的节点集合
for (auto& [u, v] : nedge) {
u = dis[u] + 1; // 转回1-based
v = dis[v] + 1;
}
度数比较策略:
cpp复制if (deg[u] > deg[v]) swap(u, v); // 统一处理顺序
if ((1 == deg[u]) && (deg[v] > iOtherNeed)) {
// 当k=0时iOtherNeed=2,否则为0
continue; // 跳过新生成节点的边
}
边的保留条件:
离散化处理:
cpp复制class CDiscretize { // 离散化类
public:
CDiscretize(vector<int> nums) {
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
m_nums = nums;
for (int i = 0; i < nums.size(); i++) {
m_mValueToIndex[nums[i]] = i;
}
}
// ... 其他成员函数
};
cpp复制vector<pair<int, int>> Ans(const int M, const int K, vector<pair<int,int>>& edge) {
const int iOtherNeed = (0 == K) ? 2 : 0;
vector<int> deg(M);
// 统计度数
for (auto& [u, v] : edge) {
u--, v--;
deg[u]++; deg[v]++;
}
vector<pair<int, int>> nedge;
vector<int> ps;
// 筛选有效边
for (auto& [u, v] : edge) {
if (deg[u] > deg[v]) swap(u, v);
if ((1 == deg[u]) && (deg[v] > iOtherNeed)) {
if (0 == K) deg[u]--, deg[v]--;
continue;
}
nedge.emplace_back(u, v);
ps.emplace_back(u);
ps.emplace_back(v);
}
// 离散化处理
CDiscretize dis(ps);
for (auto& [u, v] : nedge) {
u = dis[u] + 1;
v = dis[v] + 1;
}
return nedge;
}
使用快速输入类CInBuff提高大数据量下的IO效率:
cpp复制CInBuff<> in;
int M,K;
in >> M >> K;
auto edge = in.Read<pair<int, int>>(M-1);
auto res = Solution().Ans(M,K,edge);
cout << res.size()+1 << "\n";
for (const auto& [u, v] : res) {
cout << u << " " << v << "\n";
}
输入:
code复制5 1
1 2
1 3
1 4
1 5
处理过程:
输入:
code复制7 0
1 2
1 3
1 4
1 5
1 6
1 7
处理过程:
输入:
code复制9 1
1 2
2 3
1 4
1 5
2 6
2 7
3 8
3 9
处理过程:
度数计算错误:
k=0情况处理不当:
离散化遗漏:
小数据测试:
度数打印调试:
cpp复制#ifdef _DEBUG
for (int i = 0; i < deg.size(); i++) {
cout << i << ":" << deg[i] << " ";
}
cout << endl;
#endif
可视化工具:
度数限制变化:
部分x值已知:
加权树的情况:
网络拓扑还原:
社交网络分析:
版本控制系统:
在处理这类问题时,理解树的基本性质和掌握高效的图遍历算法是关键。这道题目很好地结合了理论分析和实际编码能力,对于提升图论问题的解决能力很有帮助。