E18【模板】树上背包 P2014 [CTSC1997] 选课
视频链接:E18【模板】树上背包 P2014 [CTSC1997] 选课_哔哩哔哩_bilibili
题意:有 n 个物品和一个容量是 V 的背包。物品之间具有依赖关系,构成一棵树。如果选择一个物品,则必须选择它的父节点。
思路:在树上做背包DP
- 以每个结点为根的子树看做一个物品组,可能选择:只选根节点,根节点+某些子结点
- f[u][j]:以u为根的子树,体积为j时的最大价值
- dfs在遍历到 u 结点时,符合条件的,先选上根节点 u,即 f[u][v[u] ~ V] = w[u]
- j的范围 [V, v[u]] ,小于v[u]则没有意义,因为连根结点都放不下
- k的范围 [0, j-v[u]],当大于 j-v[u] 时分给儿子树的容量过多,剩余的容量连根节点的物品都放不下了
#include<iostream> #include<cstring> using namespace std; const int N=110; int n,V,p,root; int v[N],w[N]; int h[N],to[N],ne[N],tot; //邻接表 int f[N][N]; void add(int a,int b){ to[++tot]=b;ne[tot]=h[a];h[a]=tot; } void dfs(int u){ for(int i=v[u];i<=V;i++) f[u][i]=w[u]; for(int i=h[u]; i; i=ne[i]){ //子节点 int s=to[i]; dfs(s); for(int j=V;j>=v[u];j--) //体积 for(int k=0;k<=j-v[u];k++)//决策 f[u][j]=max(f[u][j],f[u][j-k]+f[s][k]); } } int main(){ cin>>n>>V; //物品个数,背包容量 for(int i=1;i<=n;i++){ cin>>v[i]>>w[i]>>p; //体积,价值,依赖的物品编号 if(p==-1) root=i; else add(p,i); } dfs(root); cout<<f[root][V]; }
f[u][j]:以 u 点为根的子树,选了 j 门课程的最大学分
递推起点 f[u][1]=w[u]
01背包,逆序枚举 j
f[u][j]=max(f[u][j], f[u][j-k]+f[v][k]), j∈[m+1,1], k∈[0,j-1]
虚拟根节点0:从0号点向没有先修课的课程连边,把森林转化成一颗树
0号点的为必选点,所以共选 m+1门
// 树上背包 O(n^2) #include <iostream> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int N=305; vector<int> e[N]; int n,m,w[N],f[N][N],siz[N]; void dfs(int u){ f[u][1]=w[u];siz[u]=1; for(int v:e[u]){ dfs(v); siz[u]+=siz[v]; for(int j=min(m+1,siz[u]);j;j--) //课程 for(int k=0;k<=min(j-1,siz[v]);k++) //决策 f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]); } } int main(){ scanf("%d%d",&n,&m); for(int i=1,k; i<=n; i++){ scanf("%d%d",&k,&w[i]); e[k].push_back(i); } dfs(0); //虚拟根节点0 printf("%d",f[0][m+1]); }
// 树上背包 O(n^2) #include <iostream> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int N=305; vector<int> e[N]; int n,m,w[N],f[N][N],siz[N]; void dfs(int u){ for(int v:e[u]){ dfs(v); siz[u]+=siz[v]+1; for(int j=min(m,siz[u]); j; j--) //课程 for(int k=0; k<=min(j-1,siz[v]); k++) //决策 f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+w[v]); } } int main(){ scanf("%d%d",&n,&m); for(int i=1,k; i<=n; i++){ scanf("%d%d",&k,&w[i]); e[k].push_back(i); } dfs(0); //虚拟根节点0 printf("%d",f[0][m]); }
// 树上背包 O(n^2) #include <iostream> #include <cstring> #include <algorithm> #include <vector> using namespace std; const int N=305; int head[N],idx,w[N],to[N],ne[N]; void add(int x,int y,int z){ w[++idx]=z;to[idx]=y;ne[idx]=head[x];head[x]=idx; } int n,m,f[N][N],siz[N]; void dfs(int u){ for(int i=head[u];i;i=ne[i]){ int v=to[i]; dfs(v); siz[u]+=siz[v]+1; for(int j=min(m,siz[u]); j; j--) //课程 for(int k=0; k<=min(j-1,siz[v]); k++) //决策 f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+w[i]); } } int main(){ scanf("%d%d",&n,&m); for(int i=1,k,w; i<=n; i++){ scanf("%d%d",&k,&w); add(k,i,w); } dfs(0); printf("%d",f[0][m]); }