AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 1074. 二叉苹果树【有依赖背包DP】    原题链接    简单

作者: 作者的头像   一只野生彩色铅笔 ,  2021-09-02 23:52:16 ,  所有人可见 ,  阅读 5645


72


16

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

给定一棵含有 $n$ 个结点的树,树根编号为 $1$,且树上的每条边有一个边权 $w_{edge_j}$

要求我们只保留树中的 $m$ 条边,使得 树根 所在的 连通块 的所有边 边权之和 最大

分析

想复习一下 背包DP 的同学,可以看看我之前写过的这篇 背包DP合集
这篇题解对于重复部分的知识不会再额外讲了QwQ

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

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

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

常用的 有依赖的背包DP 集合划分方案有两个:

  1. 子物品体积集合划分 $O(N \times V \times V)$ AcWing 10. 有依赖的背包问题【有依赖背包DP+子物品体积集合划分】
  2. 分组背包集合划分 $O(N \times 2^k \times V)$ AcWing 487. 金明的预算方案【有依赖背包DP+分组背包集合划分】

对于本题来说,分组背包集合划分 会超时,因此采用 体积集合划分方案

闫氏DP分析法

状态表示—集合 $f_{i,j}$: 以 $i$ 为根节点的子树,包含 $i$ 的连通块的边数不超过 $j$ 的方案
状态表示—属性 $f_{i,j}$: 方案的边权之和最大 $Max$
状态计算—$f_{i,j}$
$$ f_{i,j} = \max\Big\{{ f_{i,j-1-k} + f_{son_i,k} + w_{edge_i} }\Big\} \quad k\in[0,j-1] $$

【注】这里 $k$ 最大枚举到 $j-1$ 是因为如果计算 该节点 对 父节点 的 贡献,必须保留他到 父节点 的 入边

Code

时间复杂度: $O(N \times V \times V)$

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, M = N << 1;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int f[N][N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int father)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int ver = e[i];
        if (ver == father) continue;
        dfs(ver, u);
        for (int j = m; j >= 0; j -- )
            for (int k = 0; k <= j - 1; k ++ )  //枚举体积预留一条连向父节点的边
                f[u][j] = max(f[u][j], f[u][j - k - 1] + f[ver][k] + w[i]);
    }
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    dfs(1, -1);
    printf("%d\n", f[1][m]);
    return 0;
}

26 评论


用户头像
不爱做有氧   2022-02-09 23:35      3    踩      回复

f[u][j - k - 1] 和 f[ver][k]会有选择边相同而导致答案错误的情况嘛

用户头像
一只野生彩色铅笔   2022-02-09 23:47      6    踩      回复

$j$ 是从大到小枚举的,因此一定用的是旧状态更新,肯定不会用到当前正在枚举的子树

用户头像
不爱做有氧   2022-02-10 00:05    回复了 一只野生彩色铅笔 的评论         踩      回复

谢谢铅笔大大 明白了

用户头像
是阿杰啊啊啊   2022-10-16 14:31    回复了 一只野生彩色铅笔 的评论         踩      回复

orz

用户头像
louisdlee   2024-03-24 16:50      1    踩      回复

实际上 u应该由左右两个儿子的答案组成,然而dfs过程中,如果你先dfs左儿子,那么你是不会有右儿子的状态。
f[u][j - k - 1] 的含义其实是左儿子选了 j-k-1
然而并没有关系,因为当你循环到右儿子的时候,f[ver][k]是右儿子的信息,而f[u][j - k - 1]是(左儿子或右儿子)的最优的状态,
所以答案还是会被正确更新


用户头像
baikun   2024-03-03 15:50      1    踩      回复

有依赖的背包 连的单向边 这题连的双向边 因为样例给的是一条边两端的节点 并没说这条边的方向(即从哪个点连向另一个)


用户头像
Nothingispossible   2021-09-22 23:23      1    踩      回复

大佬,为什么枚举体积要从大到小呀,是减了那一维呀。。

用户头像
一只野生彩色铅笔   2021-09-22 23:26         踩      回复

同01背包一样呀~

用户头像
此账号已注消   2022-01-29 20:48         踩      回复

对,就是减掉了一维所以才从大到小

用户头像
shinax   2022-12-05 12:19      14    踩      回复

本来是三维的状态,f[x][i][j],表示以x为根的子树,的前i个子节点子树中选,总体积最大为j的所有集合,
每次去循环以x节点为跟的子树,并循环体积,一个子树相当于一个组,一组内不同方案,相当于选择不同体积,
只看后面两个[i] [j]代表的是在前i个组中选,总体积不超过j的所有集合,属性max,然后还要枚举每一组内,分配多少体积,才能求出最大值
所以f[x] [i] [j] = max ( f[x] [i] [j],f[x] [i-1] [j-k] + f[y] [yi] [k])
然后进行优化,由于每次都用到上一维的状态,和子树y最后yi的状态,所以可以把第二维优化变成
f[x] [j] = max(f[x] [j] , f[x] [j-k] + f[y] [k])


用户头像
Enki   2021-09-22 19:35      1    踩      回复

大佬,问下枚举体积和决策的时候,为啥这题体积j是从m枚举到0,决策k从0到j-1。有依赖的背包问题体积j从m-v[u]枚举到0,决策k从0到j啊。

用户头像
一只野生彩色铅笔   2021-09-22 19:45      4    踩      回复

有依赖背包问题中,体积这个值是在点上的,因此枚举到 $m-v_u$

而该问题的体积是在边上的,这就是两题不一样的地方

在边上的话,要获得子树的价值,就要额外保留一条从父到子的边,这也是为什么 $k$ 从 $0$ 到 $1$ 的原因

用户头像
一只野生彩色铅笔   2021-09-22 19:47    回复了 一只野生彩色铅笔 的评论      1    踩      回复

勘误:$k$ 从 $0$ 到 $j-1$

用户头像
Enki   2021-09-22 19:54    回复了 一只野生彩色铅笔 的评论         踩      回复

明白了感谢,给大佬点赞~


用户头像
Peter_5   2021-09-02 23:53      1    踩      回复

彩铅巨巨是只勤奋的小蜜蜂❤️

用户头像
一只野生彩色铅笔   2021-09-02 23:54      3    踩      回复

希望在10月前更完DP,然后要离开几个月hh

用户头像
Peter_5   2021-09-02 23:55    回复了 一只野生彩色铅笔 的评论      2    踩      回复

会想你的😢

用户头像
一只野生彩色铅笔   2021-09-02 23:56    回复了 Peter_5 的评论      1    踩      回复

😂

用户头像
user.banned   2023-06-07 16:19    回复了 一只野生彩色铅笔 的评论      1    踩      回复

想你+1

用户头像
user.banned   2023-06-07 16:20    回复了 一只野生彩色铅笔 的评论      1    踩      回复


用户头像
自律的俗人   2024-02-13 14:08         踩      回复

佬我想问下你那个状态表示是不是有点问题,因为你要是边数不超过j的话,f[1][m]不一定是恰好m条边,我觉得你要输出f[1][m]的话你得初始化memset(f,-0x3f,sizeof f);
for(int i=1;i<=n;i++)
f[i][0]=0
这样才能恰好,我感觉yxc的代码也没有初始化只是数据弱所以能过

用户头像
自律的俗人   2024-02-13 14:12         踩      回复

这题的要求是恰好m个边


用户头像
小森   2022-03-07 16:52         踩      回复

大佬为什么是f[u][j-k-1]而不是f[u][j-k]呢为什么要多减一个1这和有依赖的背包问题貌似不一样啊

用户头像
一只野生彩色铅笔   2022-03-07 16:54         踩      回复

代码注释里写了

用户头像
小森   2022-03-07 17:06    回复了 一只野生彩色铅笔 的评论         踩      回复

刚刚看y总课恍然大悟了,谢谢


用户头像
天生丽质难自弃   2021-09-29 07:09         踩      回复

懂了懂了


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息