286. 选课(算法竞赛进阶指南,背包类树形 DP)

一.题目链接:

选课

二.题目大意:

有 n 门课,从中选出 m 门课,每门课有各自的学分,当选择课程 i 时,课程 i 的前置课程也必须选(至多一个).

求最大学分.

三.分析:

由于课程 i 至多只有一个前置课程,因此呈森林结构,设置 0 好号节点后,便成为了 n + 1 的点 n 条边的树.

设 dp[u][j] 表示以节点 u 为根的子树中,选择 j 门课程学分的最大值,c[k] 表示 u 的第 k 棵子树中选择了 c[k] 门课.

则有状态转移方程:dp[u][j] = \underset{\sum_{k = 1}^{|son(u)|}c_k = j - 1}{max}(\sum_{k = 1}^{|son(u)|}dp[v_k][c_k]) + score[u]

即对于节点 u 来说,他有 |son(u)| 组物品可以选,第 i 组的第 j 个物品体积为 j

扯不下去了,详见代码吧.

四.代码实现:

#include 
using namespace std;

const int M = (int)3e2;

int n, m;

int cnt;
int head[M + 5];
struct node
{
    int v, nx;
} Edge[M + 5];

int score[M + 5];
int dp[M + 5][M + 5];

void init()
{
    cnt = 0;
    for(int i = 0; i <= n; ++i)
    {
        head[i] = -1;
    }
}

void add(int u, int v)
{
    Edge[cnt].v = v;
    Edge[cnt].nx = head[u];
    head[u] = cnt++;
}

void dfs(int u)
{
    dp[u][0] = 0;
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
        int v = Edge[i].v;
        dfs(v);
        for(int j = m; j >= 0; --j)
        {
            for(int k = 0; k <= j; ++k)
                dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
        }
    }
    if(u)
    {
        for(int i = m; i >= 1; --i)
            dp[u][i] = dp[u][i - 1] + score[u];
    }
}

int main()
{
//    freopen("input.txt", "r", stdin);
    scanf("%d %d", &n, &m);
    init();
    for(int i = 1, fa; i <= n; ++i)
    {
        scanf("%d %d", &fa, &score[i]);
        add(fa, i);
    }
    dfs(0);
    printf("%d\n", dp[0][m]);
    return 0;
}

 

你可能感兴趣的:(#,树形,DP)