E18【模板】树上背包 P2014 [CTSC1997] 选课

视频链接:E18【模板】树上背包 P2014 [CTSC1997] 选课_哔哩哔哩_bilibili

题意:有 n 个物品和一个容量是 V 的背包。物品之间具有依赖关系,构成一棵树。如果选择一个物品,则必须选择它的父节点。

思路:在树上做背包DP

  1. 以每个结点为根的子树看做一个物品组,可能选择:只选根节点,根节点+某些子结点
  2. f[u][j]:以u为根的子树,体积为j时的最大价值
  3. dfs在遍历到 u 结点时,符合条件的,先选上根节点 u,即 f[u][v[u] ~ V] = w[u]
  4. j的范围 [V, v[u]] ,小于v[u]则没有意义,因为连根结点都放不下
  5. 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];
}

Luogu P2014 [CTSC1997] 选课

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]);
}

 

posted @ 2023-04-10 10:03  董晓  阅读(825)  评论(0编辑  收藏  举报