动态规划|285. 没有上司的舞会

树形dp的题目,yxc说这是最经典的树形dp题,所以想把这题弄明白些。

原本不想写题解的,因为难度确实不大,但是阳了之后逻辑能力被严重耽误,很那思考多一些的东西,感觉大脑的“内存”不够了,遂借助文章的形式梳理一遍题目,顺便写下来充实博客。


核心是以下两个状态转移方程,等下解释这两个转移方程的内含。
d p [ i ] [ 0 ] = ∑ m a x ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 1 ] ) dp[i][0] = \sum max(dp[son][0],dp[son][1]) dp[i][0]=max(dp[son][0],dp[son][1]) d p [ i ] [ 1 ] = ∑ d p [ s o n ] [ 0 ] + h a p p y [ i ] dp[i][1] = \sum dp[son][0]+happy[i] dp[i][1]=dp[son][0]+happy[i]
为什么说是树形的dp呢?因为树上每个结点都是可以一个状态的集合,当做状态来去转移,也就可使用动态规划了。


思路:根据题目,观察可知,父结点的状态,可以由子结点转移过来,这样一来,就可以使用dfs的方式遍历整个树,然后进行比较root结点两个状态中那个最大即可。

如何转移:每个结点有两种状态,取值或者不取;
当结点i取值时,其最大值为所有子节点不取值的和以及i结点自己的值;
当结点i不取值时,其子节点们就可以取值了,这样一来,i结点的最大值为其所有子节点最大值的和(取值或者不取值~);
根据以上思路,可得转移方程:
d p [ i ] [ 0 ] = ∑ m a x ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 1 ] ) dp[i][0] = \sum max(dp[son][0],dp[son][1]) dp[i][0]=max(dp[son][0],dp[son][1]) d p [ i ] [ 1 ] = ∑ d p [ s o n ] [ 0 ] + h a p p y [ i ] dp[i][1] = \sum dp[son][0]+happy[i] dp[i][1]=dp[son][0]+happy[i]
因此,只要从底层遍历数组即可,而dfs正好满足从底层遍历的要求,因此可以直接使用dfs遍历树的同时进行计算。

在这里插入图片描述这个dp是从最底层开始走的,如同从图中7,8开始。底层策略决定了上层的决策,这何尝不是一种现实体验呢。

import java.util.*;

/**
 * @desc 使用acwing(oj)用的模板
 */
public class Main {
    
    //定义容器、常数、读入
    int N = 6010;
    HashMap<Integer,List<Integer>> tree = new HashMap<>();
    int[][] f = new int[N][N];
    int n;
    boolean[] findRoot;
    Scanner jin = new Scanner(System.in);
    
    //oj要用的main方法
    public static void main(String[] args) {new Main().run();}//在oj中调用题解方法

    void run() {

        //读入题目数据
        n = jin.nextInt();
        
        for(int i = 1; i <= n; i++){
            f[i][1] = jin.nextInt();//读入数据+初始化f
        }
        
        findRoot = new boolean[n+1];
        
        //构造树
        for(int i = 0; i < n-1; i++){
            int son = jin.nextInt();
            int father = jin.nextInt();
            findRoot[son] = true;
            if(tree.get(father) == null) tree.put(father,new ArrayList<>());
            tree.get(father).add(son);
        }
        
        //调用解题方法
        int root = -1;
        for(int i = 1; i <= n; i++){
            if(!findRoot[i]){
                root = i;
                break;
            }
        }
        dfs(root);
        int res = Math.max(f[root][1],f[root][0]);
        
        //输出题解答案
        System.out.println(res);
    }

    void dfs(int root) {
        List<Integer> sons = tree.get(root);
        if(sons == null) return;
        for(int son : sons){
            dfs(son);
            f[root][1]+=f[son][0];
            f[root][0]+=Math.max(f[son][1],f[son][0]);//只有循环结束后,f[root][0]才更新完毕
        }   
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值