洛谷P2014 选课(树形DP+分组背包)
题目描述
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 NNN 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择 MMM 门课程学习,问他能获得的最大学分是多少?
输入格式
第一行有两个整数 NNN , MMM 用空格隔开。( 1≤N≤3001 \leq N \leq 3001≤N≤300 , 1≤M≤3001 \leq M \leq 3001≤M≤300 )
接下来的 NNN 行,第 I+1I+1I+1 行包含两个整数 kik_i ki和 sis_isi, kik_iki 表示第I门课的直接先修课,sis_isi 表示第I门课的学分。若 ki=0k_i=0ki=0 表示没有直接先修课(1≤ki≤N1 \leq {k_i} \leq N1≤ki≤N , 1≤si≤201 \leq {s_i} \leq 201≤si≤20)。
输出格式
只有一行,选 MMM 门课程的最大得分。
输入输出样例
输入 #1
7 4 2 2 0 1 0 4 2 1 7 1 7 6 2 2
输出 #1
13
#include <bits/stdc++.h> using namespace std; int n,m; vector<int> v[305];//v[i]存储i的儿子节点 int dp[305][305]={0};//dp[i][j]表示从i为根的子树里选j门课所取得的最大分数 int score[305];//第i门课的分数为score[i] void dfs(int x) { dp[x][0]=0;//显然啥课都不选 dp值为0 int i; for(i=0;i<v[x].size();i++)//枚举x的每个子节点 { int y=v[x][i];//y是子节点 dfs(y);//先dfs处理了子树 这样后面才能转移 int t,k; for(t=m;t>=0;t--)//背包容量 { for(k=0;k<=t;k++)//“在子节点中选k门课”构成一个物品 这里正序倒序都能ac { if(t>=k)dp[x][t]=max(dp[x][t],dp[x][t-k]+dp[y][k]);//进行转移 选/不选取最大 } } } if(x!=0)//x不是虚拟节点的话 { int t; for(t=m;t>0;t--)//t不能取0 { dp[x][t]=dp[x][t-1]+score[x];//必然要加上老大 因为x是它子节点的先修课 所以t里面一定包含x这门课 } } } int main() { cin>>n>>m; int i; for(i=1;i<=n;i++) { int x,y; scanf("%d%d",&x,&y); if(x==0) { v[0].push_back(i); } else v[x].push_back(i); score[i]=y; } dfs(0);//从虚拟节点开始处理 cout<<dp[0][m];//从0的子树里选m个课程 虚拟节点自然被忽略了 return 0; }
注意虚拟零节点的使用以及分组背包的条件,物品泛化等。