DP-树形DP-luogu2014-选课

题目大意

学生要在n门课程中选m门课程,每门课程最多依赖于一门前置课程(可以有多门课程没有前置课程),问能获得的最大学分是多少。(1<=m<=n<300)

问题分析

  1. 显然,每门课程依赖至多一门,有课程可以没有依赖,所以这是一个多叉树森林
  2. 森林不太好处理,所以我们可以给森林加个虚拟根节点#0,让每棵树的根节点连到这个总结点上。问题即转化为在这个多叉树#0上选m门课程的最大学分,自然而然地,我们得到了DP状态 d p i , j dp_{i, j} dpi,j,即在i子树上选择j门课程可以得到的最大学分
  3. 按照二叉树的思维来想,i子树的j门课程可以分配给它的子树们,即
    d p [ i ] [ j ] = ∑ d p [ s o n i ] [ k i ] + p n t [ i ]   ,   w h e r e   ∑ k i = j − 1 dp[i][j] = \sum dp[son_i][k_i] + pnt[i]\ , \ where \ \sum k_i = j - 1 dp[i][j]=dp[soni][ki]+pnt[i] , where ki=j1
  4. 但显然,由于i子树可能有很多儿子,这样排列组合下来复杂度很高,所以这样不可取。当然,多叉树直接算的办法还是有的,但我目前没有完全理解(确实不好理解)。事实上,我们可以曲线救国。既然二叉树的办法套在多叉树上不好用,那我们就干脆把多叉树变成二叉树——孩子兄弟树
  5. 孩子兄弟树,即左子树是孩子,右子树是兄弟。显然,选i子树的孩子课程就得选父亲课程,但是选兄弟课程就不会受i子树的影响,因此DP状态 d p i , j dp_{i, j} dpi,j,即在i子树(i和i的孩子、i的兄弟)上选择j门课程可以得到的最大学分,转移式为
    d p [ i ] [ j ] = { d p [ b r o i ] [ j ] m a x { d p [ s o n i ] [ k ] + d p [ b r o i ] [ j − k − 1 ] + p n t [ i ] } dp[i][j] = \begin{cases} dp[bro_i][j]\\ max \{dp[son_i][k] + dp[bro_i][j - k - 1] + pnt[i] \} \end{cases} dp[i][j]={dp[broi][j]max{dp[soni][k]+dp[broi][jk1]+pnt[i]}
  6. 对应上式的情况分别为
    1. 不选课程i,只选i兄弟的课程
    2. 选i课程, 再选 i的孩子课程和兄弟课程共计j-1门
  7. 此处采取了DFS的方式保证了遍历顺序,准确的讲是记忆化搜索

AC代码

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <cstdlib>

using namespace std;
const int maxn = 310;

int fa[maxn], son[maxn], bro[maxn], val[maxn];//fa[]是不必要的
int dp[maxn][maxn];

inline void add(int f, int s){bro[s] = son[f], son[f] = s, fa[s] = f;}

int dfs(int i, int j)
{
    if(i == -1)return 0;//走到空位置
    if(j == 0)return 0;//剩余选择数为0门
    if(dp[i][j] != -1)return dp[i][j];
    int temp = -1 << 30;
    temp = max(temp, dfs(bro[i], j));
    for(int k = 0; k <= (j - 1); k++)
        temp = max(temp, dfs(son[i], k) + dfs(bro[i], j - k - 1) + val[i]);
    return dp[i][j] = temp;
}

int main()
{
    memset(dp, -1, sizeof(dp));
    memset(son, -1, sizeof(son));
    memset(bro, -1, sizeof(bro));
    int n, m;
    scanf("%d%d", &n, &m);
    for(int s = 1; s <= n ; s++)
    {
        int f,v;
        scanf("%d%d", &f, &v);
        add(f, s); val[s] = v;
    }
    printf("%d\n", dfs(0, m + 1));
    system("pause");
    return 0;
}

参考博客

  1. https://www.luogu.org/blog/user15011/solution-p2014 (多叉树转二叉树,大赞!)
  2. https://www.luogu.org/blog/105496/solution-p2014 (分析了两种方法,多叉/二叉)
  3. https://www.luogu.org/blog/user25845/solution-p2014
    (多叉树法,分析得不错,但多叉有个地方我理解不了~)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值