2024-02-19(树形DP)

文章介绍了AcWing题库中涉及的树形深度优先搜索(DFS)在寻找树的最长路径、直径、中心以及与数字转换和背包问题中的应用,展示了如何使用邻接表数据结构和动态规划思想解决这些问题。
摘要由CSDN通过智能技术生成

285. 没有上司的舞会 - AcWing题库树形dp的一道题2024-02-07(计数类DP、数位统计DP、状态压缩DP、树形 DP、记忆化搜索)-CSDN博客

1072. 树的最长路径 - AcWing题库 

找树的直径

1、任取一点作为起点,找到距离该点最远的点u  BFS, DFS

2、再找到距离u点最远的点v   BFS, DFS

        那么u和v之间的路径就是一条直径 

import java.util.*;

public class Main{
    static int N = 10010, M = 2 * N;//因为是无向图,所以要开两倍的数组
    static int[] h = new int[M], ne = new int[M], e = new int[M], w = new int[M];
    static int n, res, idx;//res表示答案
    
    //邻接表存储无向图
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static int dfs(int u, int father){
        int dist = 0;//表示这个点往下走的最大长度
        int d1 = 0;//这个点往下走的最大长度
        int d2 = 0;//这个点往下走的第二大长度
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            if(j == father) continue;//由于只能往下走,如果等于父节点就说明往回走了,所以不行
            
            int d = dfs(j, u) + w[i];
            dist = Math.max(dist, d);
            if(d > d1){
                d2 = d1;
                d1 = d;
            }else if(d > d2){
                d2 = d;
            }
        }
        
        res = Math.max(res, d1 + d2);
        return dist;
    }
    
    //开始main函数
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        
        Arrays.fill(h, -1);
        for(int i = 1; i < n; i ++){//一共有n-1条边
            int a = sc.nextInt();
            int b = sc.nextInt();
            int c = sc.nextInt();
            add(a, b, c);
            add(b, a, c);
        }
        
        dfs(1, -1);//取编号为1的点作为根节点,由于其没有父节点,所以让其父节点的参数为-1
        
        System.out.print(res);
    }
}

 

1073. 树的中心 - AcWing题库

暴力枚举:求出每个点到其他所有点的最远距离,然后取一个最小值 

import java.util.*;

public class Main{
    static int N = 10010, M = 2 * N, idx, n, INF = 0x3f3f3f3f;
    static int[] h = new int[M], ne = new int[M], e = new int[M], w = new int[M];//邻接表
    static int[] d1 = new int[M];//往下走最长距离
    static int[] d2 = new int[M];//往下走次长距离
    static int[] p = new int[M];//记录最长路径是由哪个节点过来的
    static int[] up = new int[M];//往上走的最长距离
    
    //邻接表存储
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    //往下走找最长距离
    public static int dfs_down(int u, int father){
        d1[u] = 0;
        d2[u] = 0;
        
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            if(j == father) continue;
            
            int d = dfs_down(j, u) + w[i];
            
            if(d > d1[u]){
                d2[u] = d1[u];
                d1[u] = d;
                p[u] = j;
            }else if(d > d2[u]){
                d2[u] = d;
            }
        }
        
        return d1[u];
    }
    
    //往上走的最长距离
    //有两个方向:父节点接着往上走,父节点往下走
    public static void dfs_up(int u, int father){
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            if(j == father) continue;
            
            //如果父节点往下走的最长距离是经由这个节点来的,那么就用次长和往上走的距离比较,最后要加上这个点到父节点的距离
            if(p[u] == j) up[j] = Math.max(d2[u], up[u]) + w[i];
            else up[j] = Math.max(d1[u], up[u]) + w[i];
            
            dfs_up(j, u);
        }
    }
    
    //开始main函数
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        
        Arrays.fill(h, -1);//初始化
        
        for(int i = 1; i < n; i ++){
            int a = sc.nextInt();
            int b = sc.nextInt();
            int c = sc.nextInt();
            add(a, b, c);
            add(b, a, c);
        }
        
        dfs_down(1, -1);//往下走
        dfs_up(1, -1);//往上走
        
        int res = INF;
        for(int i = 1; i <= n; i ++){
            res = Math.min(res, Math.max(up[i], d1[i]));//找到每个点的两个方向的最大值,再从中取最小
        }
        
        System.out.print(res);
    }
}

 

1075. 数字转换 - AcWing题库

每一个数的约数之和(不包括本身)都是固定的。将一个数(能转换)的约数之和作为它的父节点。那么问题就转化为 求树的最长路径问题。 

import java.util.*;

public class Main{
    static int N = 50010;
    static int[] h = new int[N], ne = new int[N], e = new int[N];
    static int idx, n, res;
    static boolean[] st = new boolean[N];
    static int[] sum = new int[N];//约数之和
    
    //邻接表存储
    public static void add(int a, int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    //dfs树的最长路径模板
    public static int dfs(int u){
        int d1 = 0;
        int d2 = 0;
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            int d = dfs(j) + 1;
            if(d > d1){
                d2 = d1;
                d1 = d;
            }else if(d > d2){
                d2 = d;
            }
        }
        res = Math.max(res, d1 + d2);
        return d1;
    }
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        
        Arrays.fill(h, -1);//初始化
        
        //遍历1到n,判断这个数可以是哪些数的约数
        for(int i = 1; i <= n; i ++){
            for(int j = 2; j <= n / i; j ++){
                sum[i * j] += i;
            }
        }
        
        for(int i = 1; i <= n; i ++){
            if(i > sum[i]){
                add(sum[i], i);//建立有向图,约数之和为其父节点
                st[i] = true;//有父节点
            }
        }
        
        for(int i = 1; i <= n; i ++){
            if(!st[i]) dfs(i);//如果没有父节点的话,就遍历这一颗树
        }
        
        dfs(1);
        System.out.print(res);
    }
}

 

1074. 二叉苹果树 - AcWing题库

10. 有依赖的背包问题 - AcWing题库的简化版2024-02-12(背包模型)-CSDN博客

 

import java.util.*;

public class Main{
    static int N = 110, M = 2 * N;
    static int[] h = new int[M], ne = new int[M], e = new int[M];
    static int idx, n, m;
    static int[][] f = new int[M][M];//f[u][j]以u为根节点的树选j条边的最大价值
    static int[] w = new int[M];//每一条边的价值
    
    //邻接表存储
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static void dfs(int u, int father){
        for(int i = h[u]; i != -1; i = ne[i]){//先枚举物品组
            int son = e[i];
            if(son == father) continue;//由于是无向图,所以要保证不往回走
            
            dfs(son, u);//递归!!!进行子节点的遍历
            for(int j = m; j >= 0; j --){//再从大到小枚举体积
                for(int k = 0; k < j; k ++){//最后再枚举决策
                    //这里j先减去1,是因为要留一条边给连接他的父节点
                    f[u][j] = Math.max(f[u][j], f[u][j - 1 - k] + f[son][k] + w[i]);
                }
            }
        }
    }
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        
        Arrays.fill(h, -1);
        for(int i = 1; i < n; i ++){
            int a = sc.nextInt();
            int b = sc.nextInt();
            int c = sc.nextInt();
            add(a, b, c);//建立无向图
            add(b, a, c);
        }
        
        dfs(1, -1);
        System.out.print(f[1][m]);
    }
}

323. 战略游戏 - AcWing题库

285. 没有上司的舞会 - AcWing题库 这道题很像2024-02-07(计数类DP、数位统计DP、状态压缩DP、树形 DP、记忆化搜索)-CSDN博客

没有上司的舞会:每条边上最多选一个点,求最大权值 。

战略游戏:每条边上至少选一个点,求最小权值。

1.  substring()方法-CSDN博客 

2.  indexOf()方法的使用,截取字符串,字符串截取,切割字符串,split(),join(),Replace()_val.indexof-CSDN博客

当题目说有多组测试数据,但又没有说明有多少组时,用while(sc.hasNext()) 

import java.util.*;

public class Main{
    static int N = 1510;
    static int[] h = new int[N], ne = new int[N], e = new int[N];
    static int idx, n;
    static int[][] f = new int[N][2];//f[u][0] f[u][1]
    static boolean[] st = new boolean[N];//用来判断根节点
    
    //邻接表存储
    public static void add(int a, int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static void dfs(int u){
        //f[u][0] = 0;//初始化
        f[u][1] = 1;
        
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            dfs(j);//递归
            //dp思想分析
            f[u][0] += f[j][1];
            f[u][1] += Math.min(f[j][1], f[j][0]);
        }
    }
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        while(sc.hasNext()){//多组测试数据
            n = sc.nextInt();
            
            //由于有多组测试数据,所以每次都要进行初始化
            idx = 0;
            Arrays.fill(h, -1);
            Arrays.fill(st, false);
        
            for(int i = 0; i < n; i ++){
                String str = sc.next();
                //输入
                int id = Integer.parseInt(str.substring(0, str.indexOf(":")));
                int cnt = Integer.parseInt(str.substring(str.indexOf('(') + 1, str.length() - 1));
                for(int j = 1; j <= cnt; j ++){
                    int b = sc.nextInt();
                    add(id, b);
                    st[b] = true;
                }
            }
        
            int root = 0;
            while(st[root]) root ++;
            dfs(root);
        
            System.out.println(Math.min(f[root][1], f[root][0]));//找到两个状态最小的那一个
        }
    }
}

 

1077. 皇宫看守 - AcWing题库

战略游戏那道题看的是边,皇宫看守这道题看的是点。 

 

import java.util.*;

public class Main{
    static int N = 1510;
    static int n, idx;
    static int[] h = new int[N], ne = new int[N], e = new int[N];
    static int[] w = new int[N];
    static boolean[] st = new boolean[N];
    static int[][] f = new int[N][3];
    //f[u][0]表示点u没有放警卫,且点u被父节点看到的所有方案中的最小花费
    //f[u][1]表示点u没有放警卫,且点u被子节点看到的所有方案中的最小花费
    //f[u][2]表示在点u上放警卫的所有方案中的最小花费
    
    public static void add(int a, int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static void dfs(int u){
        f[u][2] = w[u];//放警卫的花费
        int sum = 0;
        
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            
            dfs(j);//递归
            f[u][0] += Math.min(f[j][1], f[j][2]);//u被父节点看到,u的子节点的情况
            sum += Math.min(f[j][1], f[j][2]);//用于f[u][0]的计算,其实这里的sum可以不用要,因为已经记录在f[u][0]里面了
            f[u][2] += Math.min(Math.min(f[j][0], f[j][1]), f[j][2]);//u放警卫,u的儿子节点的情况
        }
        
        f[u][1] = 0x3f3f3f3f;//要求最小值,先置为无穷大
        for(int i = h[u]; i != -1; i = ne[i]){//自己放警卫的情况,要讨论它的所有子节点
            int j = e[i];//当枚举到字节点j,子节点j放警卫,其他子节点放与不放都可以,找到最小花费的
            f[u][1] = Math.min(f[u][1], f[j][2] + (sum - Math.min(f[j][1], f[j][2])));
        }
    }
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        
        Arrays.fill(h, -1);
        for(int i = 1; i <= n; i ++){
            int a = sc.nextInt();
            w[a] = sc.nextInt();
            int cnt = sc.nextInt();
            while(cnt -- > 0){
                int b = sc.nextInt();
                add(a, b);
                st[b] = true;
            }
        }
        
        int root = 1;
        while(st[root]) root ++;
        
        dfs(root);
        
        System.out.print(Math.min(f[root][1], f[root][2]));
    }
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值