C++树形DP之树上的有依赖性的背包问题——选课

前言

此题为初学树形DP的学者们必做的一道题。因为此题涉及到了树上的背包问题,它的依赖性被抽象成了点与点之间的关系,也就是一棵树。学会了这道题,其他的树形背包也万变不离其中。

此题可以说是一道比较简单的题,并没有许多复杂的关系,所以很适合初学者们。

介意之前没学过树形DP的同学先去预习一下树形DP入门题,了解一下树形DP用dfs实现的最基础的结构。

题目

问题 H(1376): 【基础算法】选课

时间限制: 1 Sec  内存限制: 64 MB

题目描述

学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。 学校开设了 N 门的选修课程,每个学生可选课程的数量 M 是给定的。学生选修了这M门课并考核通过就能获得相应的学分。 在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows 操作基础》之后才能选修。我们称《Windows 操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为1,2,3,…。例如:

表中 1 是 2 的先修课,2 是 3、4 的先修课。如果要选 3,那么 1 和 2 都一定已被选修过。 你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。

输入

第1行:2个空格分开的整数 N、M,其中 1≤N≤300,1≤M≤N。

接下来N 行每行代表一门课。课号依次为1,2,…,N。每行有2个空格分开的整数,第一个数为这门课先修课的课号(若不存在先修课则该项为 0),第二个数为这门课的学分。 学分是不超过10 的正整数。

输出

第1行:只有一个数,即实际所选课程的学分总数。

样例输入

7 4 
2 2 
0 1 
0 4 
2 1 
7 1 
7 6 
2 2 

样例输出

13

分析 

  相信很多人看了这道题都会想到金明的预算方案,是的这两道题思路都大致相同。但不同的是,金明的预算方案最多有2个附件,此题的先修课就像附件,每门课都可能有多个先修课,先修课还会有先修课,所以单纯的线性DP已经不能满足我们的需求了。

仔细想想你就会发现,课与课之间的关系就像点与点之间的边,先修课就是这些节点的父亲,而每门课的学分就相当于点权。将题中的样例画成树便是:

 因为总有一门课没有先修课,所以我们可以将0,也就是没有先修课作为根节点来DP。

根据我们前面的树形DP的经验,我们可以用DFS深搜到叶子节点,再返回来(因为选的肯定是所有课的先修课)。

现在我们来考虑DP的状态转移方程。

先来考虑数组f是几维的吧。

从叶子节点开始,根据背包的经验,第一维肯定是到节点i的最大可以选的学分点。

因为题目规定最多只能选m门课,所以光一维肯定是不够的。所以还要加一维为:选了j门课的最大学分值.

所以数组f_{ij}的意义就是到节点i之后选了j门课的最大学分值。

再来考虑一下重头戏,状态转移方程怎么写。

首先,f_{ij}肯定是由节点i的儿子们转移过来的。设到节点i选了j门课,如果节点i的儿子选了k门课,那么节点i就选了j-k门课,我们只需在里面取一个最大值便可。

这里需要注意一点,枚举j时一定要从大到小枚举。

所以我们的策略便是从大到小枚举一个j,表示总共选了j门课,再枚举一个k,表示儿子选了k门课,然后转移取个最大值即可。

状态转移方程:

f_{i,j}=max(f_{son,k}+f_{i,j-k})

代码

#include <cstdio>
#include <vector>
#include <iostream>
 
using namespace std;
 
#define N 310
 
int cnt,n,m,f[N][N];
vector <int> G[N];
 
 
void dfs(int x) {
    for(int i=0;i<G[x].size();i++) {
        int son=G[x][i];//节点x的儿子
        dfs(son);
        for(int j=m+1;j>=1;j--)//DP
            for(int k=0;k<j;k++)
                f[x][j]=max(f[x][j],f[son][k]+f[x][j-k]); 
    }
}
 
int main() {
    scanf("%d %d",&n,&m);
    for(int i=1,u;i<=n;i++) {
        scanf("%d %d",&u,&f[i][1]);//这里把f[i][1]相当于有一个初始化,这样就不用再另开一个数组
        G[u].push_back(i);
    }
    dfs(0);
    cout<<f[0][m+1];
}

猜你喜欢

转载自blog.csdn.net/weixin_44049566/article/details/88110155