logo AlgoBeat OnlineJudge
登录 注册

ChatGPT 题解

作者: Murasame  ·  发布于 2026-07-03 10:49:09  ·  最后修改于 2026-07-03 10:50:42
已通过
审核员:Murasame · 2026-07-03 10:50:42

1. 题意整理

给你一棵树,每个点 有一个权值

你可以选择一些边,使得任意两条被选中的边不能共用端点,也就是说这些边组成一个匹配

规则是:

  • 没有被匹配边覆盖到的点,自己承担自己的代价
  • 如果一条边 被选中,那么这两个点合起来只算

目标是让总代价最小。


2. 这类题为什么适合树形 DP

因为图是一棵树,边之间没有环。

这意味着当我们以某个点为根后,每个儿子子树之间是互相独立的。

只要我们知道“当前节点和父亲之间是什么关系”,就可以把每个子树单独算出来,再组合起来。


3. 状态设计

我们把树根设为

定义两个状态:

表示:

  • 没有和父亲配对
  • 可以选择:
    • 自己单独承担代价
    • 或者和某一个儿子配对

也就是说, 这一层是“自由”的。

表示:

  • 已经和父亲配对了
  • 因此 不能再和任何儿子配对

这里要注意:
与父亲配对这一条边的代价,不在这个状态里算。
这个代价会在父亲决定选这条边的时候一起算掉。


4. 先看 taken[u] 怎么求

如果 已经和父亲配对了,那么 自己不能再去配对儿子。

所以每个儿子 都只能处于 free[v] 状态。

因此:

这里的 的所有儿子。

这一步非常自然:
父亲把 “占用了”,于是 的子树只能继续独立处理。


5. 再看 free[u] 怎么求

free[u] 有两种情况。

情况一: 不和任何儿子配对

就自己承担代价 ,每个儿子各自处理:

情况二: 和某个儿子 配对

这时候:

  • 的代价是
  • 不能再和自己的父亲配对,所以 必须进入 taken[v]
  • 其他儿子仍然是 free 状态

所以代价是:

把公共部分提出来,就得到:


6. 合并公式

所以:

其中 遍历 的每个儿子。


7. 为什么这样是对的?

因为树上的每条边只连接父子。

当我们确定了:

  • 是否和父亲配对
  • 是否和某一个儿子配对

以后,其他子树就互不影响了。

所以最优解一定可以拆成各个儿子子树的最优解之和,再加上当前这一层的选择成本。

这正是树形 DP 的核心思想。


8. 如何高效计算

如果直接对每个儿子都重新求一遍和,会重复计算很多次。

我们先算:

那么:

  • taken[u] = sumFree
  • 如果 不和儿子配对,答案是
  • 如果 和儿子 配对,答案是

这样每个点只扫一遍儿子,总复杂度就是


9. 复杂度分析

每条边只会被处理常数次,所以总复杂度为:

题目里 ,这个复杂度是完全可以通过的。


10. 参考代码

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

int n;
vector<ll> a;
vector<vector<int>> g;
vector<ll> free_dp, taken_dp;
vector<int> parent_node, order_nodes;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    a.assign(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }

    g.assign(n + 1, {});
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    parent_node.assign(n + 1, 0);
    order_nodes.clear();
    order_nodes.reserve(n);

    stack<int> st;
    st.push(1);
    parent_node[1] = -1;

    while (!st.empty()) {
        int u = st.top();
        st.pop();
        order_nodes.push_back(u);
        for (int v : g[u]) {
            if (v == parent_node[u]) {
                continue;
            }
            parent_node[v] = u;
            st.push(v);
        }
    }

    free_dp.assign(n + 1, 0);
    taken_dp.assign(n + 1, 0);

    for (int idx = n - 1; idx >= 0; --idx) {
        int u = order_nodes[idx];

        ll sumFree = 0;
        for (int v : g[u]) {
            if (v == parent_node[u]) {
                continue;
            }
            sumFree += free_dp[v];
        }

        taken_dp[u] = sumFree;

        ll best = a[u] + sumFree;

        for (int v : g[u]) {
            if (v == parent_node[u]) {
                continue;
            }
            ll cand = max(a[u], a[v]) + taken_dp[v] + (sumFree - free_dp[v]);
            best = min(best, cand);
        }

        free_dp[u] = best;
    }

    cout << free_dp[1] << '\n';
    return 0;
}

暂无评论

登录 后即可评论。