选课方案 / 选课
题目链接:ybt高效进阶5-4-4 / luogu P2014
题目大意
有一个森林,你可以选 m 个点,这些点要满足它们的父亲都被选到,然后要你最大化选的点权和。
思路
考虑树形 DP。
然后发现范围可以
O
(
n
3
)
O(n^3)
O(n3),可以直接放一个背包上去。
设
f
i
,
j
f_{i,j}
fi,j 为搞定
i
i
i 的子树,
i
i
i 要选,选了
j
j
j 个的最大点权和。
然后就从下向上转移,大概就是枚举总共的 j j j,再枚举这个子树用了多少次。
然后我们发现它是森林,你要合并答案。
然后就会自然的想到用
0
0
0 号点把森林变成树,然后要的次数就是
m
+
1
m+1
m+1 次,
0
0
0 号点的点权是
0
0
0,然后即可以了。
代码
#include<cstdio>
#include<iostream>
using namespace std;
struct node {
int to, nxt;
}e[301];
int n, m, a[301], fa[301];
int le[301], KK, f[301][302];
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
}
void dfs(int now, int father) {
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
dfs(e[i].to, now);
for (int j = m; j >= 0; j--) {//两个枚举一定都要是倒序
for (int k = j; k >= 0; k--)//背包(枚举两边各选的个数)
f[now][j] = max(f[now][j], f[now][j - k] + f[e[i].to][k]);
}
}
for (int i = m; i >= 1; i--)//选,也要倒序
f[now][i] = f[now][i - 1] + a[now];
}
int main() {
scanf("%d %d", &n, &m);
m++;
for (int i = 1; i <= n; i++) {
scanf("%d %d", &fa[i], &a[i]);
add(fa[i], i);
}
dfs(0, 0);
printf("%d", f[0][m]);
return 0;
}