P2015 二叉苹果树

GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。

一、先回答问题

结论

dp[u][j] 表示 u 子树内保留 j 条边最多留住多少苹果。

容易误入的路

只贪心保留苹果最多的 Q 条边,会破坏必须与根连通的限制。

二、核心观察

建模与压缩

每个孩子子树是一组选择,分配 k 条边给它时还要加上连接孩子的 1 条边。

为什么不丢答案:子树之间只通过 u 相连,保留边数可以独立分配后做背包合并。

dp[u][j] 孩子 v 分配 k

三、互动演示

步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推

四、自己选答案

问题 1

为什么要 +1 条边?

问题 2

j 为什么倒序?

五、手推结果

转移项意义
dp[u][j-k-1]留给已处理部分
dp[v][k]孩子子树
+w连接孩子的边

课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。

六、C++14 参考代码

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

int n, Q, dp[105][105];
vector<pair<int,int> > g[105];   // {孩子, 边权}

void dfs(int u, int fa) {
    for (size_t i = 0; i < g[u].size(); i++) {
        int v = g[u][i].first, w = g[u][i].second;
        if (v == fa) continue;
        dfs(v, u);
        for (int j = Q; j >= 1; j--)           // 当前总边数,从大到小
            for (int k = 0; k <= j - 1; k++)   // 分给 v 子树 k 条 + 连 v 的边
                dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + w);
    }
}

int main() {
    cin >> n >> Q;
    for (int i = 1; i < n; i++) {
        int a, b, c; cin >> a >> b >> c;
        g[a].push_back(make_pair(b, c));
        g[b].push_back(make_pair(a, c));
    }
    dfs(1, 0);
    cout << dp[1][Q] << "\n";
    return 0;
}