AcWing 286. 选课

题目传送门

#include <bits/stdc++.h>

using namespace std;
const int N = 310;
int n, m;
int w[N];
int f[N][N];
//创建邻接表
int h[N], e[N], ne[N], idx;

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

//分组背包过程
void dfs(int u) {
    for (int i = h[u]; i != -1; i = ne[i]) {//物品组
        dfs(e[i]);
        for (int j = m - 1; j >= 0; j--)    //体积
            for (int k = 0; k <= j; k++)    //决策
                f[u][j] = max(f[u][j], f[u][j - k] + f[e[i]][k]);
    }
    //把老大加上去,同样由于滚动数组对于自己本行的依赖,所以采用了倒序
    //给老大预留了一个位置1,所以i>=1
    for (int i = m; i >= 1; i--) f[u][i] = f[u][i - 1] + w[u];
    //要注意0这个边界,当当前节点的选课数量为0时是没有意义的,所以需要初始化为0,但是其本身就是0,
    //所以这里直接不循环即可,类比一下10_2.cpp
    //f[u][0]=0;
}

int main() {
    /**
    树形DP+01背包-->映射成分组背包

    如果有三个子树,表示有三个物品组,每个物品组中有m+1(0~m)个物品
    第k个物品的体积是k,价值:f(s1,k)

    第一层:物品组,第二层:从大到小循环体积,第三层:决策
    时间复杂度:O(n*m^2)

    但是这道题可能不只有一个点没有父结点,而是有好多点都可能没有父结点,这时,我们可以把0看成虚拟的根结点,最终我们取f[0,m+1]即可
    同时,因为每选一门课,都需要添加上0这个结点,所以最终是m+1
    */
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) {
        int p;
        cin >> p >> w[i];
        //添加虚拟根的技巧
        add(p, i);//若不存在先修课则该项为0:这为我们创建虚拟根0号结点创造了条件!!!
    }
    //所有方案都需要添加虚拟根结点0,就相当于选了m+1门课
    m++;
    dfs(0);
    cout << f[0][m] << endl;//由于m已经++,所以这里就是f[0][m]了
    return 0;
}
posted @ 2022-01-28 08:19  糖豆爸爸  阅读(119)  评论(0编辑  收藏  举报
Live2D