经典树形DP问题,
f[i][j]
表示当前在i节点,并且当前已经选的课数量是j,的最大学分数量
难点在于对dfs的理解,这里处理到每一层的时候,直接对每一个子节点进行递归,但实际求解是一个回溯的过程,分组背包的方式也与经典分组背包不同,这里基于闫氏dp的思想,以集合为概念,就可以将每组子节点分开来求了,用每一组子节点的每一个状态来处理当前节点每一种选课不同数量集合的方式。
这里有一些小点,我们新建虚拟节点0,其学分为0,这样可以将森林转化为树,然后在一个dfs里面处理完毕,同时选了一个学分为0点空节点,我们的总选课上限也要加一,在后面的便利过程中,每一次处理完当前节点的左右子节点后,需要在for外面将自己本身加上,保证了数据的完整性,在题目所有的循环中,要注意0这个边界,当当前节点的选课数量为0时是没有意义的,所以需要初始化为0,但是其本身就是0,所以这里直接不循环即可,
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 310;
int h[N], e[N], ne[N], idx;
int w[N], f[N][N];
int n, m;
void add(int a, int b){
e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}
void dfs(int u){//把以u节点为根的子树处理完毕
//枚举当前节点的邻接表
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
dfs(j);
//开始分组背包
for(int k = m; k >= 0; k --)//从大到小枚举,这里利用了滚动数字,经典问题
for(int s = 1; s <= k; s ++)
f[u][k] = max(f[u][k], f[u][k-s] + f[j][s]);//处理当前所有子树的集合
}
//前面处理了当前为根的树的所有子树,现在来处理自己,需要将自己加上去
for(int i = m; i >= 1; i --)
f[u][i] = f[u][i-1] + w[u];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i <= n; i ++)
{
int a, b; cin >> a >> b;
add(a, i); w[i] = b;
}
m ++;
dfs(0);
cout << f[0][m] << endl;
return 0;
}
讲的太好了!!
讲的太好了!!