最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一棵含有 $n$ 个结点的树,树根编号为 $1$,且树上的每条边有一个边权 $w_{edge_j}$
要求我们只保留树中的 $m$ 条边,使得 树根 所在的 连通块 的所有边 边权之和 最大
分析
想复习一下 背包DP 的同学,可以看看我之前写过的这篇 背包DP合集
这篇题解对于重复部分的知识不会再额外讲了QwQ
这题的题面其实就是 有依赖的背包 模型,不同的是把 物品价值 分给了 边 而不是 点
不过,对于一棵树来说,任意节点的入边(连向父节点的边) 都是 唯一 的
所以 边权 和 点权 在确定树的 根节点 以后,是可以视作一个东西的(入边价值 视作 该点价值)
常用的 有依赖的背包DP 集合划分方案有两个:
- 子物品体积集合划分 $O(N \times V \times V)$ AcWing 10. 有依赖的背包问题【有依赖背包DP+子物品体积集合划分】
- 分组背包集合划分 $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;
}
f[u][j - k - 1] 和 f[ver][k]会有选择边相同而导致答案错误的情况嘛
$j$ 是从大到小枚举的,因此一定用的是旧状态更新,肯定不会用到当前正在枚举的子树
谢谢铅笔大大 明白了
orz
实际上 u应该由左右两个儿子的答案组成,然而dfs过程中,如果你先dfs左儿子,那么你是不会有右儿子的状态。
f[u][j - k - 1] 的含义其实是左儿子选了 j-k-1
然而并没有关系,因为当你循环到右儿子的时候,f[ver][k]是右儿子的信息,而f[u][j - k - 1]是(左儿子或右儿子)的最优的状态,
所以答案还是会被正确更新
有依赖的背包 连的单向边 这题连的双向边 因为样例给的是一条边两端的节点 并没说这条边的方向(即从哪个点连向另一个)
大佬,为什么枚举体积要从大到小呀,是减了那一维呀。。
同01背包一样呀~
对,就是减掉了一维所以才从大到小
本来是三维的状态,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])
大佬,问下枚举体积和决策的时候,为啥这题体积j是从m枚举到0,决策k从0到j-1。有依赖的背包问题体积j从m-v[u]枚举到0,决策k从0到j啊。
有依赖背包问题中,体积这个值是在点上的,因此枚举到 $m-v_u$
而该问题的体积是在边上的,这就是两题不一样的地方
在边上的话,要获得子树的价值,就要额外保留一条从父到子的边,这也是为什么 $k$ 从 $0$ 到 $1$ 的原因
勘误:$k$ 从 $0$ 到 $j-1$
明白了感谢,给大佬点赞~
彩铅巨巨是只勤奋的小蜜蜂❤️
希望在10月前更完DP,然后要离开几个月hh
会想你的😢
😂
想你+1
佬我想问下你那个状态表示是不是有点问题,因为你要是边数不超过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的代码也没有初始化只是数据弱所以能过
这题的要求是恰好m个边
大佬为什么是f[u][j-k-1]而不是f[u][j-k]呢为什么要多减一个1这和有依赖的背包问题貌似不一样啊
代码注释里写了
刚刚看y总课恍然大悟了,谢谢
懂了懂了