洛谷P2014 [CTSC1997] 选课 题解

本题做法

  • 树形DP+背包DP。

思路

这题中可能存在不止一棵树,为了方便处理,我们将所有树的根节点都连接到父节点0,统一成一棵树处理,此时\(M\)应该加1。

本题中,我们可以将一门课看作“一个背包问题中的物品”,其体积、价值分别为\textbf{1}和\(s\)。为什么体积是1呢?因为一门课会占据\(M\)门可选课中的1门课,所以体积为1;价值为\(s\)很好理解,因为它可以加\(s\)点学分。

这样,这题就转化成了一道经典的“有依赖的背包问题”。我们令\(dp[u][i][j]\)表示节点\(u\)的前\(i\)个节点分配\(j\)门课的最大学分,将0-1背包嵌套在树形背包里面,这就是DP套DP的雏形。根据定义,我们可以分成2种情况考虑:选或不选节点\(u\)的第\(i\)个子节点(记作\(v\))。若选择节点\(v\),枚举分配给节点\(v\)\(k\)门课选择,则对应的下一个状态为\(dp[u][i-1][j-k]+dp[v][num_v][k]\)(这里不用加\(s_v\)的原因后面会讲);若不选择节点\(v\),则对应的下一个状态为\(dp[u][i-1][j]\)。所以得到状态转移方程:

\[dp[u][i][j]=\max\{dp[u][i-1][j-k]+dp[v][num_v][k],dp[u][i-1][j]\} \]

注意到,\(dp\)数组的第二维只用得到\(i-1\),所以我们可以把第二维优化掉,变成二维DP,状态转移方程如下:

\[dp[u][i]=\max_{k=0}^{i-1}\{dp[u][i-j]+dp[v][j],dp[u][i]\} \]

DP的状态设计、状态转移方程我们已经解决了,现在只剩下边界条件了。这很简单,很容易注意到:如果当前只分配了一门课可供选择,当然是只能选择当前节点\(u\)了,所以边界条件就是\(dp[u][1]=s_u\),因为选择子节点的价值已经包含在边界条件(即上面状态转移方程中的\(dp[v][j]\))了,所以就不用加上\(s_v\)了。

根据上面的思路,就可以写出题目代码了。

代码

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

constexpr int INF=0x3f;
constexpr double EPS=1e-8;
constexpr int N=305;

int n,m,s[N],dp[N][N];
vector<int> g[N];

void dfs(int u){
    for(int v:g[u]) dfs(v);
    for(int v:g[u]){//子节点
        for(int i=m;i>=1;i--){//dp[u][i]
            for(int k=0;k<i;k++){
                dp[u][i]=max(dp[u][i],dp[u][i-k]+dp[v][k]);
            }
        }
    }
}

int main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    cin>>n>>m;
    m++;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x>>s[i];
        dp[i][1]=s[i];
        g[x].push_back(i);
    }
    dfs(0);
    cout<<dp[0][m]<<endl;
    return 0;
}
posted @ 2026-01-09 21:42  EnjoySilence  阅读(3)  评论(0)    收藏  举报