##[$AcWing$ $1074$ . 二叉苹果树](https://www.acwing.com/problem/content/1076/) **[视频讲解](https://www.bilibili.com/video/av668731451)** ### 一、题目描述 有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的节点。 这棵树共 $N$ 个节点,编号为 $1$ 至 $N$,树根编号一定为 $1$。 我们用一根树枝两端连接的节点编号描述一根树枝的位置。 一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,**给定需要保留的树枝数量,求最多能留住多少苹果**。 这里的保留是指最终与$1$号点连通。 **输入格式** 第一行包含两个整数 $N$ 和 $Q$,分别表示树的节点数以及要保留的树枝数量。 接下来 $N−1$ 行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。 **输出格式** 输出仅一行,表示最多能留住的苹果的数量。 **数据范围** $1≤Q 解析: > - 一个普适情况,讨论第$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 当为$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])$$ ### 三、实现代码 ```cpp {.line-numbers} #include 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个,kv这边条上的苹果数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; } ```