
1. 什么是树上贪心树上贪心Tree Greedy是一种在树形数据结构如树、图等上应用贪心思想的算法策略。它通常用于解决树上的最优化问题如最小覆盖、最大匹配、路径选择等。贪心算法的核心是“每一步都做出当前看来最优的选择”而树上贪心则将这一思想与树的结构特性如父子关系、子树独立性等相结合。2. 核心思想与适用场景2.1 核心思想局部最优推导全局最优在树的每个节点或每条边上做出局部最优决策期望这些决策能组合成全局最优解。利用树的结构树的无环性和层次性使得很多问题具有最优子结构适合自底向上后序遍历或自顶向下先序遍历的贪心。决策的独立性某些问题中不同子树的最优解可以独立计算最后在根节点合并。2.2 典型适用场景最小点覆盖/边覆盖选择最少的点/边覆盖所有边/点。最大独立集选择最多的点使得任意两点不相邻。树形DP的贪心优化将DP状态转移简化为贪心选择。路径问题如最小代价路径覆盖、最长不重复路径等。资源分配在树形网络上分配资源使总效益最大。3. 基础算法框架树上贪心通常采用深度优先搜索DFS遍历树在递归返回时进行贪心决策。以下是一个通用框架// 以无根树为例邻接表存储 vectorvectorint g; vectorbool visited; // 后序遍历DFS返回子树的相关信息 pairint, int dfs(int u, int parent) { visited[u] true; // 初始化状态 int take 0, not_take 0; for (int v : g[u]) { if (v parent) continue; auto [child_take, child_not_take] dfs(v, u); // 根据贪心策略合并子树结果 // 例如对于最小点覆盖 // take min(child_take, child_not_take); // not_take child_take; } // 根据当前节点是否选择更新状态 // take ...; // not_take ...; return {take, not_take}; } // 调用从任意根节点开始例如0 // auto [root_take, root_not_take] dfs(0, -1); // 答案 min(root_take, root_not_take);4. 经典问题实战4.1 树的最小点覆盖问题描述选择最少的点使得每条边至少有一个端点被选中。贪心策略自底向上对于每个节点u和其父节点p如果u是叶子节点不选u让父节点p来覆盖边(u,p)。如果u的某个子节点未被覆盖则必须选u。否则暂时不选u把决策推迟给父节点p。代码实现vectorvectorint adj; vectorbool covered; int ans 0; void dfs(int u, int parent) { bool need_cover false; for (int v : adj[u]) { if (v parent) continue; dfs(v, u); if (!covered[v]) need_cover true; } if (need_cover) { covered[u] true; covered[parent] true; ans; } } // 初始化 covered 为 false // dfs(0, -1); // 假设0为根且0的父节点为-1 // 输出 ans4.2 树的最大独立集问题描述选择最多的点使得任意两个被选点不相邻。贪心策略自底向上优先选择叶子节点因为不选叶子就可能要选其父节点可能减少可选点。将树视为无根树每次找到度数为1的叶子节点选择它。删除该叶子及其邻居更新度数。重复直到树为空。代码实现贪心解法int maxIndependentSet(int n, vectorpairint, int edges) { vectorunordered_setint g(n); for (auto [u, v] : edges) { g[u].insert(v); g[v].insert(u); } vectorbool selected(n, false); vectorbool removed(n, false); queueint leaves; for (int i 0; i n; i) { if (g[i].size() 1) leaves.push(i); } int ans 0; while (!leaves.empty()) { int leaf leaves.front(); leaves.pop(); if (removed[leaf]) continue; // 选择这个叶子 selected[leaf] true; ans; removed[leaf] true; // 删除它的邻居 if (!g[leaf].empty()) { int neighbor *g[leaf].begin(); removed[neighbor] true; // 更新邻居的邻居的度数 for (int nn : g[neighbor]) { if (nn ! leaf) { g[nn].erase(neighbor); if (g[nn].size() 1) leaves.push(nn); } } } } return ans; }5. 贪心正确性证明思路树上贪心的正确性通常需要严谨证明常见方法交换论证证明任何最优解可以通过有限次交换调整为贪心解。归纳法对树的高度或节点数进行归纳。剪枝性质证明贪心选择后剩余子问题与原问题具有相同结构。拟阵理论某些问题可以转化为拟阵上的贪心。例如树的最小点覆盖的贪心正确性证明对于边(u,v)如果u是叶子那么覆盖(u,v)的唯一方法是选v。贪心策略中当发现叶子u未被覆盖时就选其父节点v这覆盖了边(u,v)。这个选择不会比最优解差因为任何覆盖边(u,v)的解都必须选u或v而选v可能还能覆盖其他边。6. 常见陷阱与优化6.1 常见陷阱贪心策略不具全局最优性某些树形问题需要DP贪心只能得到近似解。忽略树的有根/无根有根树通常更容易设计贪心但要注意根的选择是否影响结果。处理不了后效性如果当前选择会影响之前已做决策贪心可能失效。6.2 优化技巧结合二分答案当问题具有单调性时用二分将最优化转化为判定问题再用贪心检查。多叉树转二叉树某些贪心策略在二叉树上更易实现。预处理子树信息提前计算子树大小、深度等加速贪心决策。7. 总结树上贪心是一种强大而直观的算法思想它将贪心的“局部最优”与树结构的“层次性”巧妙结合。掌握树上贪心的关键在于识别问题是否具有贪心选择性质。设计合理的遍历顺序通常后序遍历。在递归返回时合并子树信息并做出决策。对正确性进行证明或至少用反例验证。通过本文介绍的经典问题和代码框架读者可以尝试解决更多树上优化问题并逐渐培养出识别贪心可行性的直觉。