题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?

输入格式

第一行有两个整数N,M用空格隔开。(1<=N<=300,1<=M<=300)

接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第i门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。

输出格式

只有一行,选M门课程的最大得分。

解决问题

“先修课”的关系就如同树上的父子节点一样.因此通过以”先修课关系”为依据连好边以后,这个问题的模型将呈现树形.这是一道树形DP的经典题目.我们设f[i][j]表示在以i为根的这棵树内选取k门课,所能得到的最大学分和.为什么能这样呢?因为树形非常符合DFS的模型,能够做到不重不漏,所以树形DP通常用递归的方式实现.那么我们显然需要考虑到设置状态的时候应该与什么有关.DFS时,我们总是一次次到达子树的根节点,然后再继续考虑,所以我们应当将其中一维与子树的根相关.然后我们想要得到最大学分,这要通过适当的选择科目来实现.因此我们枚举选择j门课,能得到的最大学分.最终目标f[0][m].

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <utility>
#include <vector>
using namespace std;//树形DP经典例题_选课 

inline void read(int &x) {
    x = 0;
    char ch = getchar(), w = 0;
    while (!isdigit(ch)) w = (ch == '-'), ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    x = w ? -x : x;
    return;
}

int n, m, sc[305], f[305][305];//f[i][j]表示以i为根的子树中选j门课能得到的最多学分 
vector<int> son[305];

void tree_dp(int now) {//树形DP惯用记忆化搜索 
    f[now][0] = 0;//初值为0; 
    for (int i = 0; i < son[now].size(); ++i) {//扫描所有儿子 
        int v = son[now][i];
        tree_dp(v);//递归到底再从叶子返回根 
        for (int t = m; t >= 0; --t)//倒序枚举选几门课,因为每一节课只能选一次,代表这属于01背包,如果正序就是完全背包了. 
            for (int j = t; j >= 0; --j)
                if (t - j >= 0)
                    f[now][t] = max(f[now][t], f[now][t - j] + f[v][j]);//相当于从目前枚举出的总课数中,
                                                                    //有j门是在儿子i选的,其他的是在其他儿子选的. 
    }
    if (now != 0)
        for (int t = m; t > 0; --t)
            f[now][t] = f[now][t - 1] + sc[now];//因为树根也要选,所以所有情况都要加上树根 
    return;
}

int main() {
    read(n), read(m);
//    memset(f, 128, sizeof(f));
    for (int i = 1, a, b; i <= n; ++i) {//建树,读入score 
        read(a), read(b);
        son[a].push_back(i), sc[i] = b;
    }
    tree_dp(0);//从树根开始dp ,这个树根是超级原点 
    cout << f[0][m] << endl;//最终目标,在以0为根的子树中选m门课的最高学分 
    return 0;
}

欢迎批评和指正

——Gensokyo