P2014 [CTSC1997] 选课 树形dp

解题思路

问题分析

这道题是一个典型的树形依赖背包问题,需要在一棵课程依赖树中选择若干门课程,满足:

  1. 选择某门课程前必须先选择它的先修课程

  2. 总共选择 M 门课程

  3. 获得的学分总和最大

动态规划状态设计

定义 dp[u][j]:表示在以 u 为根的子树中选择 j 门课程能获得的最大学分

状态转移方程

对于每个节点 u 和它的子节点 v:

  1. 如果不选 u,则不能选任何子节点

  2. 如果选 u,则可以分配 k 门课程给子节点 v 的子树

  3. 转移方程:
    dp[u][j] = max(dp[u][j], dp[u][j-1-k] + dp[v][k] + w[v])

    解释:

    • j-1-k:给其他子树分配的课程数(总课程数 j 减去当前课程 1 再减去给子树的 k)

    • dp[v][k]:子树 v 选择 k 门课程的最优解

    • w[v]:课程 v 的学分

关键点说明

  1. 虚拟根节点:代码中使用 0 作为虚拟根节点连接所有无先修课的课程

  2. 倒序枚举 j:类似背包问题的空间优化,避免重复计算

  3. 课程数限制:M 门课程包含所有选择的课程(包括先修课程)

代码注释

#include<bits/stdc++.h>
using namespace std;

const int N = 1e3 + 10;    // 数组大小
vector<int> g[N];          // 邻接表存储课程树,g[u]表示u的所有子课程
int n, m;                  // n-课程总数,m-需要选择的课程数
int w[N];                  // w[i]表示第i门课程的学分
int dp[N][N];              // dp[u][j]: 以u为根的子树选择j门课程的最大学分

void dfs(int u) {
    // 遍历u的所有子课程
    for(int i = 0; i < g[u].size(); i++) {
        int v = g[u][i];  // 子课程v
        dfs(v);  // 递归处理子课程
        
        // 动态规划转移(类似背包问题倒序枚举)
        for(int j = m; j >= 1; j--) {          // 枚举当前可选的课程总数
            for(int k = 0; k <= j - 1; k++) {  // 分配给子课程v的课程数(最多j-1门)
                // 状态转移:选择u课程(隐含),并分配k门课程给v的子树
                // 因为要选v课程,必须先选u课程(u是v的先修课)
                dp[u][j] = max(dp[u][j], dp[u][j - 1 - k] + dp[v][k] + w[v]);
            }
        }
    }
}

int main() {
    cin >> n >> m;  // 输入课程总数和需要选择的课程数
    
    // 建树(0作为虚拟根节点)
    for(int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;  // x是先修课,y是当前课程的学分
        w[i] = y;       // 存储学分
        g[x].push_back(i);  // 添加到先修课的子节点中
    }
    
    dfs(0);  // 从虚拟根节点0开始DFS
    
    cout << dp[0][m];  // 输出选择m门课程的最大学分
    
    return 0;
}

 

posted @ 2025-06-12 16:33  CRt0729  阅读(29)  评论(0)    收藏  举报