【树形dp】| AcWing 算法基础班试题总结

285. 没有上司的舞会

题目描述

某公司有N名职员,编号为1~N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。

输入格式
第一行一个整数N。
接下来N行,第 i 行表示 i 号职员的快乐指数Hi。
接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。

输出格式
输出最大的快乐指数。

数据范围
1≤N≤6000,
−128≤Hi≤127

输入样例:

7
1
1
1
1
1
1
1
1 3 //即,3->1
2 3
6 4
7 4
4 5
3 5

输出样例:

5

分析

整个公司的职员组成一棵树,每个职员就是树上的一个结点,对于每个结点,有0 1两种状态,选就是1,不选就是0。

状态表示:

f[u][0]:以结点u为根的子树,并且不选u这个点的符合要求的各种方案
f[u][1]:以结点u为根的子树,并且选择u这个点的符合要求的各种方案

属性: Max,f[u][0]或者f[u][1]的值就是各种符合要求的方案的最大值

求从u结点开始的子树满足要求的方案最大值,最终结果肯定在f[u][0]f[u][1]中选择,选择其最大值。

  1. 考虑是否选择结点u
  2. 递归地考虑父结点u各个子结点si递归结束的条件就是到达叶子结点。

以父结点u为根节点考虑 和 以子节点s为根节点考虑,这二者的方法是相同的,且相互独立的。
且父结点的值需要用到子节点的值。

假设以结点u为根节点的子树有多个子节点si,考虑它的每个子节点si,可以再看成以子节点si为根的子子树。

  1. 如果选择结点u的话,子结点si一定不能选。
    f [ u ] [ 1 ] = ∑ ( f [ s i , 0 ] ) f[u][1]=∑(f[si,0]) f[u][1]=(f[si,0])
  2. 如果不选择结点u的话,子结点可选可不选,选择其中的最大值即可。
    f [ u ] [ 0 ] = ∑ m a x ( f [ s i , 0 ] , f [ s i , 1 ] ) f[u][0]=∑max(f[si,0],f[si,1]) f[u][0]=max(f[si,0],f[si,1])

注意:
由数据的输入可知,我们并不知道树的根节点是谁,因此还需要自己处理得到。
得到根节点root后,看成以根节点的子树,所以最后只用考虑root根节点是否选择,输出f[root][0]f[root][1]的较大者即可。

代码实现

#include <iostream>
#include <cstring>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=6010;
int hpy[N];
int h[N],e[N],ne[N],idx;//树,N个顶点,N-1条边
//next数组记录后继结点,通过idx记录
int pre[N];//pre数组记录前驱结点,直接通过结点编号记录
int dp[N][2];

void add(int a,int b)//a->b的边
{
    
    
    e[idx]=b,ne[idx]=h[a],h[a]=idx++; 
    pre[b]=a;
}

void dfs(int u) //判断以该点为根的子树的最大happy值
{
    
    
  "初始化,是否选该结点:dp[u][1]和dp[u][0]"
  "选中该结点,加上该happ值,不选中时,dp[u][0]=0"
    dp[u][1]=hpy[u];   
  "递归结束的条件,遇到叶子结点,它没有子节点了。只进行初始化,然后就往上返回"
    for (int i=h[u];~i;i=ne[i]) {
    
    
        int j=e[i]; //找它的子节点
        dfs(j); //求子节点的最大happy值
        //考虑以u为根的子树,是否选子节点?
        dp[u][0]+=max(dp[j][0],dp[j][1]); //不选u时,子节点可选可不选,取最大值。
        dp[u][1]+=dp[j][0]; //选u时,子节点必不能选。
    }
}

int main()
{
    
    
    memset(h,-1,sizeof h);
    int n;
    read(n);
    for (int i=1;i<=n;i++) read(hpy[i]);
    //存树
    int l,k; //k是l的直接上司,即k->l的边
    for (int i=1;i < n;i++) {
    
     //n个顶点,所以共n-1条边,不能取等号
        read(l),read(k);//先输入的是子节点
        add(k,l);  //存数k->l的边
    }
    //找树的根节点,记录结点编号,只有根节点没有父结点,pre[rt]==0
    int rt=1;
    while (pre[rt]) rt++;
    dfs(rt);
    int res=max(dp[rt][0],dp[rt][1]);
    printf("%d",res);
    
    return 0;
}

一定要记得main循环第一行中的初始化,常犯错误。

dfs递归中,递归结束的条件就是碰见叶子结点,由于没有子节点了,所以他也就不会进入for循环,进行初始化之后就往上返回了。

树形结构,不存在一个点有多个入度,只有一个入度和最多一个出度,保证了每个点只会被初始化一次,只会经历一次递归。

猜你喜欢

转载自blog.csdn.net/HangHug_L/article/details/114537073