典型的树形DP,但是做起来还是要看题解,看来还是不会吖.
题目背景
小\(A\)在果园里发现了一棵结满果子的树,于是他就打起了坏主意,他打算把树的一部分砍下来带回家。
题目描述
我们可以把这棵树表示成一个树型的结构,也就是说,任意两个点之间有且仅有一条路径。在每个点\(i\)处都结着一个水果,每个水果有一个价值\(v_i\)和重量\(w_i\)。小\(A\)想带走树的一部分(或全部),包含至少\(K\)个结点(也就是至少\(K\)个水果),且这些水果的平均价值尽可能高。平均价值是指水果总的价值除以总的重量。注意小\(A\)砍下的树必须是在原来的树中连通的一部分。
输入格式
第一行包含两个数\(N\)和\(K\),分别表示树的结点数和小\(A\)至少应带走的水果数。第二行包含空格隔开的\(N\)个数,分别表示每个结点处水果的价值\(v_i\)。第三行包含空格隔开的\(N\)个数,分别表示每个水果的重量\(w_i\)。按下来\(N-1\)行,每行包含两个数\(a_i\)和\(b_i\) (\(1 ≤ a_i, b_i ≤ N\)),表示在结点\(a_i\)和\(b_i\)之间有一条边。输入保证是一棵正确的树结构。
输出格式
输出一行,包含一个数,表示最大可能的平均价值。四舍五入到小数点后两位。
数据范围
对\(100%\)的数据,\(1 ≤ N ≤ 100, 1 ≤ K ≤ N, 1 ≤ v_i ≤ 10000, 1 ≤ w_i ≤ 10000\)
解析
看到价值和重量,就知道这是一道我不会的树上背包问题.一般背包可以设\(f_{i}\)表示选择重量不超过\(j\)的物品能获得的最大价值,但是这道题要结合树形结构,而且\(w\)和\(v\)的范围都很大,所以考虑基于个数来设置状态.
我们设\(f_{i,j}\)表示在以\(i\)为根的子树中选择\(j\)个点能获得的最大平均价值.对于它的每棵子树,我们枚举从中选择了多少节点来转移.
※因为题目要求记录平均值,而平均值并不容易直接转移,所以我们将\(f\)数组开成结构体,记录当前状态的总重量,总价值,以及平均值(总价值/总重量).这道题显然是树上\(01\)背包,因此别忘了要倒序循环外层.
struct node {
double w, v, ave;//重量,权值,平均值
node() {}
node(double a, double b, double c) : w(a), v(b), ave(c) {}
}f[105][105];
void dfs(int now, int father) {
f[now][1] = node(w[now], v[now], v[now] / w[now]);//为了维护连通性,根节点肯定要选.
for (int i = head[now]; i; i = nxt[i]) {
if (to[i] == father)//枚举每个子节点
continue;
dfs(to[i], now);//先递归下去,再由叶到根更新
for (int j = n; j > 1; --j) {//枚举以now为根的子树中一共选择多少个点,注意01背包要倒序
for (int k = 1; k <= j; ++k) {//枚举当前子树中选择了j个节点中的多少个
node p = f[to[i]][j - k];//在这个子树中选择了j个节点中的j-k个点
node q = f[now][k];//在其他子树中选择了k个点.
double ave = (p.v + q.v) / (p.w + q.w);//计算平均值
if (ave >= f[now][j].ave) {//更新答案
f[now][j] = node(p.w + q.w, p.v + q.v, ave);
}
}
}
}
return;
}
最后在所有\(f[i][j](k \le j \le n)\)中选择最大值输出即可.
感觉还是不会呢