[Luogu2014]选课 树形DP

本文介绍了一个树形DP问题的解决思路,通过构建虚拟源点将森林转换为树,并使用多叉树转二叉树的技术简化问题。文章详细解释了状态定义与转移方程,最终实现O(n³)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Luogu2014
其实一眼就秒掉了这个树形dp,然后想了想转移,然而有些细节还是调了很久
首先整个图不一定是联通的,那么我们考虑把森林转化为树,即建立一个”源点“,然后把0向所有入度为0的点加边,就变成了一棵树。
我们又发现一个节点可能有多个儿子,不好处理,于是我们可以用一个惯用的方法——多叉树转二叉树,也可以叫左儿子右兄弟表示。顾名思义,新树节点的左儿子是原树的儿子,新树节点的右儿子是这个节点的兄弟,即拥有相同的父亲。那么我们就可以跑DP了。
我们可以这样考虑:设 dp[i][j] 表示在i号节点及其儿子还有右兄弟(也就是其右下部分)中选出j节课所获得的最大收益
1. 学自己与不学自己
2. 学兄弟
3. 学儿子(枚举即可)

时间复杂度为 O(n3)

#include<iostream>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define maxn 310
using namespace std;
int n,m,w[maxn],sz[maxn];
struct node{
    int to;
    node*next;
}*con[maxn];
void addedge(int x,int y) 
{
node*p=new node;
p->to=y;
p->next=con[x];
con[x]=p;
}
int ls[maxn],rs[maxn],dp[maxn][maxn];
int dfs(int v)
{
    sz[v]=1;
    for(node*p=con[v];p;p=p->next)
    {
        dfs(p->to);
        sz[v]+=sz[p->to];
    }
    if(sz[v]==1) dp[v][1]=w[v];
}
int dfs1(int v)
{
if(ls[v]!=0)
    dfs1(ls[v]);
if(rs[v]!=0)
    dfs1(rs[v]);
if(rs[v]!=0&&ls[v]==0)
    for(int j=1;j<=m;++j) dp[v][j]=max(dp[v][j],max(dp[rs[v]][j],dp[rs[v]][j-1]+w[v]));
else 
if(ls[v]!=0&&rs[v]!=0)
    {
    for(int j=1;j<=m;++j)
        for(int k=1;k<=j;++k)
            dp[v][j]=max(dp[v][j],dp[ls[v]][k-1]+w[v]+dp[rs[v]][j-k]);
        for(int j=1;j<=m;++j) dp[v][j]=max(dp[v][j],max(dp[rs[v]][j],dp[rs[v]][j-1]+w[v]));
    }
else 
if(ls[v]!=0&&rs[v]==0) 
    for(int i=1;i<=sz[v];++i)
        dp[v][i]=max(dp[v][i],dp[ls[v]][i-1]+w[v]);
}
int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=n;++i)
    scanf("%d%d",&x,&w[i]),addedge(x,i);
    dfs(0);
    for(int i=0;i<=n;++i)
    {
    if(con[i]==NULL) continue;
    ls[i]=con[i]->to;
    node*p=con[i],*p1=con[i]->next;
        while(p1!=NULL)
            {
            rs[p->to]=p1->to;
            p=p->next;p1=p1->next;
            }
    }
    dfs1(0);
    int ans=0;
    cout<<dp[ls[0]][m];
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值