P2014 选课 - 树形DP[树形背包DP]

时间:2022-07-22 22:12:01

P2014 选课

传送门

思路:

树形背包DP模型,\(f[i,j]\)表示以\(i\)为根的子树中,选了\(j\)门课的最大学分。树形DP常以子树\(i\)为阶段。树形背包DP相当于树上分组背包DP。\(f[u,j]=max\{f[u,j],f[u,j-k]+f[v,k]~|~v\in~son(u)\}\)。我们枚举从u的子树v中选的课数k,将\(f[v,k]\)作为获得的价值加到\(f[u,j-k]\)得到\(f[u,j]\)。注意到当前子树根节点u是必须选的,所以要从\(f[u,j-1]\)加上\(a[u]\)转移到\(f[u,j]\)。最终答案为\(f[1,M]\)

Tips:

1.注意到题目给定的是一个森林,我们可以建一个虚拟节点0,将森林转化为一个以0为根节点的树,然后就可以直接树形DP了。
注意0号节点不代表课程,所以不需要从\(f[0,j-1]+a[0]\)转移到\(f[0,j]\)
2.初始化DP数组\(f[u,0]=0\)
3.对于树形DP的判重,好像记录\(vis\)数组 和 记录\(fa\)是等效的。
4.树形DP,别忘了DFS(v)啊。。。

AC Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 300+100;
int n,m;
int fa[N],a[N];
struct node{int to,nxt;}e[N<<1];
int head[N],tot;
void add(int u,int v){e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot;}
bool vis[N];int f[N][N];
void dfs(int u){
    f[u][0]=0;vis[u]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(vis[v]) continue;
        dfs(v);
        for(int j=m;j>=0;j--){
            for(int k=j;k>=0;k--){
                if(j-k>=0) f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
            }
        }
    }
    if(u!=0) for(int j=m;j;j--) f[u][j]=f[u][j-1]+a[u];
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&fa[i],&a[i]),add(i,fa[i]),add(fa[i],i);
    dfs(0);
    printf("%d",f[0][m]);
    return 0;
}