AcWing 1074 . 二叉苹果树

\(AcWing\) \(1074\) . 二叉苹果树

视频讲解

一、题目描述

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

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

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

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

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

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

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

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

数据范围
\(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;
}
posted @ 2022-01-11 14:17  糖豆爸爸  阅读(170)  评论(0编辑  收藏  举报
Live2D