树形dp:AcWing 285. 没有上司的舞会

树形dp

给定一棵N个节点的树(通常是无根树,也就是有N-1条无向边),我们可以任选一个节点为根节点,从而定义出每个节点的深度每棵子树的根

在树上设计动态规划算法时,一般就以节点从深到浅子树从小到大)的顺序作为DP的“阶段”。

DP的状态表示中,第一维通常是节点编号(代表以该节点为根的子树)。

大多数时候,我们采用递归的方式实现树形动态规划。对于每个节点x先递归在它的每个子节点上进行DP,在回溯时,从子节点向节点x进行状态转移

例题:AcWing 285. 没有上司的舞会

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其实本题是一个从一维扩展而来的题目(大盗阿福)

题意:

有N名职员,编号为1~N。他们的关系就像一棵以校长为根的树父节点就是子节点的直接上司

每个职员有一个快乐指数,用整数happy给出,其中 1≤i≤N。

现在要召开一场周年庆宴会,不过,没有职员愿意和他的直接上司一起参会。

在满足这个条件的前提下,选择部分职员参加宴会,使得所有参会职员的快乐指数happy总和最大

求这个最大值

思路

首先,如果仅仅使用f[i]表示i这个子树最多能选的分值的话,我们发现状态无法进行转移,因为在分析每一棵子树的时候,我们无从得知当前子树的根节点是否选择,所以为了能正确区分,我们需要对状态进行进一步划分(这是一种简单的状态机模型)

状态表示

我们可以划分两种情况

f [ u ] [ 0 ] f[u][0] f[u][0]:所有以 u 为根的子树中选择,并且不选 u 这个点的方案

f [ u ] [ 1 ] f[u][1] f[u][1]:所有以 u 为根的子树中选择,并且选 u 这个点的方案

属性:Max

状态计算

(1)当前 u 结点不选,子结点可选可不选

求解树形 dp 时我们是从根节点开始向下递归求解,当我们递归到 u 这个点是我们先将它的两个儿子的状态(设为s1s2处理好,即算出f[s1, 0]、f[s1, 1]、f[s2, 0]、f[s2, 1] 之后再来计算f[u, 0](所有以 u 为根的子树中选择,并且不选 u 这个点的最大值)。首先每个儿子都是独立的,若使得f[u, 0]最大,就应该使u每棵子树达到最大,它们的加和即为答案。

由于 u 没有选择,所以对于它的每棵子树(注意分析的对象是子树),既可以选择其根节点,也可以不选其根节点,比如对于某一棵子树 s 来说,则有: f [ u , 0 ] + = m a x ( f [ s , 0 ] , f [ s , 1 ] ) f[u, 0] += max(f[s, 0], f[s, 1]) f[u,0]+=max(f[s,0],f[s,1])

推出最终的式子为: f [ u ] [ 0 ] = ∑ m a x ( f [ s i , 0 ] , f [ s i , 1 ] ) ( s i 表 示 u 的 各 棵 子 树 ) f[u][0]=∑max(f[si,0],f[si,1])(si表示u的各棵子树) f[u][0]=max(f[si,0],f[si,1])(siu)

(2)当前 u 结点选,子结点一定不能选

f [ u ] [ 1 ] = ∑ ( f [ s i , 0 ] ) ( s i 表 示 u 的 各 棵 子 树 ) f[u][1]=∑(f[si,0])(si表示u的各棵子树) f[u][1]=(f[si,0])(siu)(由于选择了 u ,所以对于它的每棵子树不选其根节点

时间复杂度分析

首先一共有2n个状态,每个状态计算的时候需要计算的是它所有的儿子,又树中所有节点的儿子数量即为树当中边的数量(n-1条),所以计算所有状态时总共枚举的次数应当为:O(n-1)

最终分析的总时间复杂度为:O(n)

注意

本题输入的是一棵有根树(指定了节点间的上下属关系),故我们需要先找出没有上司的节点root作为根,

DP的目标为:max(f[root, 0], f[root, 1])

代码

写法一:用vector存图

#include<bits/stdc++.h>

using namespace std;

const int N = 6010;
int happy[N];
bool has_father[N];
int n;
int dp[N][N];
vector<int> g[N];

void dfs(int u)
{
    dp[u][0] = 0, dp[u][1] = happy[u];
    for(auto x : g[u])
    {
        dfs(x);
        //回溯的时候更新答案
        dp[u][0] += max(dp[x][0], dp[x][1]);
        dp[u][1] += dp[x][0];
    }
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) cin>>happy[i];
    for(int i=0; i<n-1; ++i)
    {
        int a, b;
        cin>>a>>b;
        has_father[a] = true, g[b].push_back(a);
    }

    int root = 1;
    while(has_father[root]) root++;
    dfs(root);
    cout<<max(dp[root][0], dp[root][1])<<endl;

    return 0;
}

写法二:用数组模拟邻接表存图

#include<bits/stdc++.h>

using namespace std;
const int N = 6010;
int n;
int happy[N];
bool has_father[N];
int h[N], e[N], ne[N], idx;
int dp[N][N];

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

void dfs(int u)
{
    dp[u][0] = 0, dp[u][1] = happy[u];
    for(int i=h[u]; ~i; i=ne[i])
    {
       int j = e[i];
       dfs(j);
       dp[u][0] += max(dp[j][0], dp[j][1]);
       dp[u][1] += dp[j][0];
    }
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) cin>>happy[i];
    memset(h, -1, sizeof h);
    for(int i=0; i<n-1; ++i)
    {
        int a, b;
        cin>>a>>b;
        add(b, a), has_father[a] = true;
    }
    int root = 1;
    while(has_father[root]) root++;
    dfs(root);
    cout<<max(dp[root][0], dp[root][1])<<endl;
    
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值