You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1074 . 二叉苹果树

视频讲解

一、题目描述

有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的节点。

这棵树共 N 个节点,编号为 1N,树根编号一定为 1

我们用一根树枝两端连接的节点编号描述一根树枝的位置。

一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果

这里的保留是指最终与1号点连通。

输入格式 第一行包含两个整数 NQ,分别表示树的节点数以及要保留的树枝数量。

接下来 N1 行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。

输出格式 输出仅一行,表示最多能留住的苹果的数量。

数据范围 1≤Q<N≤100. N≠1, 每根树枝上苹果不超过 30000 个。

输入样例:

5 2
1 3 1
1 4 10
2 3 20
3 5 20

输出样例:

21

~手绘美图方便理解~

二、题目分析

这题的题面其实就是 有依赖的背包 模型,不同的是把 物品价值 分给了 而不是

不过,对于一棵树来说,任意节点的入边连向父节点的边 都是 唯一

所以 边权点权 在确定树的 根节点 以后,是可以视作一个东西的(入边价值 视作 该点价值

树枝上苹果的数量 可以理解成 该边的权值

闫氏DP分析法

状态表示

  • 集合f[u][i][j]:以u为根的子树,在前i个子树中选,总体积不超过j的所有集 合。
  • 属性:边权值和的最大值

状态转移 $\large f[u][i][j] = f[u][i-1][j] \ \large f[u][i][j] = max(f[u][i][j],f[u][i-1][j-k-1] + f[v][v_i][k]+w[i])$

解析:

  • 一个普适情况,讨论第i棵子树,也就是以v为根的子树,下面简称为v子树,怎么处理,当前剩余的分配资源是j条边。
  • 如果v子树不选,那么就简单: f[u][i][j] = f[u][i-1][j]
  • 如果v子树选择,需要多支付1条边的代价:u->v,同时带来w[i]的利益 此时,事情变化为:为v子树内部继续提供多少条边呢?最小是0,最多是j-1条, 也就是0<=k<j 当为v子树选择k条边时: ① f[u][i-1][j-k-1]:以u为根的树中,在前i-1个子树,在j-k-1条边的限定情况下,可以获取到的最大价值 ② f[v][v_i][k]:以v为根的树中,在所有子树情况下(v_i表示v为根的树中所有子树数量),在有k条边限定的情况下,可以获取到的最大价值 ③ w[i]:不管怎样,选择了v子树,可以带来u->这条边w[i]的价值
f[u][i][j] = max(f[u][i][j],f[u][i-1][j-k-1] + f[v][v_i][k]+w[i])

Q:这里面的f[v][v_i][k]里的v_i是啥呢? :是指v子树有多少个子节点。有同学可能会表示不理解,因为创建的是一个无向图,会不会造成这些子节点和向上的父节点混淆呢?是不会的,原因是因为当根=1固定时,再加st[u]数组的使用,使得只能向叶子逼近,不能反向向根逼近,不用担心。

Q:v_i这东西怎么计算? :瞪眼大法出场!仔细观察可知,上面的状态转移方程,每一个i对是对i-1的依赖,也就是可以使用滚动数组或者倒序枚举的办法进行降维,这是01背包中由大到小的枚举顺序是完全一致的,只要使用了倒序枚举,就不会造成对上一行数据依赖的被覆盖掉。

当思考完可能降维的思路后,再仔细观察,发现f[v][v_i][k]其实本意表达的就是f[v][k],也就是在v为根节点的子树中,给出的边限定数量是k的情况下可以获取到的最大边权和。那个中间的v_i就是v节点的一级子节点个数,是一个不变量,是可以在降维过程中去掉的,不会影响结果。

三维转二维

\large f[u][j] = max(f[u][j],f[u][j-1-k]+f[v][k]+w[i])

三、实现代码

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int M = N << 1;
int n; // 表示树的节点数
int m; // 表示要保留的树枝数量(背包容量)
int h[N], e[M], w[M], ne[M], idx;
int st[N];   // 是不是访问过
int f[N][N]; // f[u][j]:表示所有以u,为树根的子树中选选j条边的最大价值
// 邻接表模板
void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// 树形DP
void dfs(int u) {
    st[u] = 1;

    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i]; // 子节点最好设置为v,不要使用j,因为j一般后面会参于循环用的变量节约资源
        if (st[v]) continue;
        dfs(v); // 因为父亲需要使用子树的信息来更新自己的信息所以必须先递归子树才能使用子树提供的信息子树提供的信息保存在f[v][1~k]中

        // 枚举体积 (u的所有可能体积)
        for (int j = m; j >= 0; j--)
            // 枚举决策 (子节点v中分配k个体积,需要给u->v留一个v子树中就少1个,k<j)
            for (int k = 0; k < j; k++)
                //  f[u][j - 1 - k]:让u从其它子树中去查找,在空间j-1-k的限定下的最大价值
                //  f[v][k]+w[i]:在v子树中资源上限是k的情况下返回的最大数量加上u->v这边条上的苹果数w[i]
                f[u][j] = max(f[u][j], f[u][j - 1 - k] + f[v][k] + w[i]);
    }
}

int main() {
    // 初始化
    memset(h, -1, sizeof h);
    cin >> n >> m;
    // 读入n-1条边
    int a, b, c;
    for (int i = 1; i < n; i++) {
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c); // 只能是无向图,因为没说谁离根更近
    }
    dfs(1);                // 已知根是1号点
    printf("%d", f[1][m]); // DP从根出发最大限制是m的情况下可以取得的最大值
    return 0;
}