欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第一章动态规划(十一)

程序员文章站 2022-07-13 11:29:49
...

树形DP

例题:树的最长路径
第一章动态规划(十一)
也叫做树的直径
步骤:

  • 任取一点,找到距离该点最远的一个点 u。—BFS
  • 再找到距离 u 最远的一个点 v。—BFS

u 和 v 之间的路径就是一条直径。
第一章动态规划(十一)

我们将每个节点作为一个类,记录两条信息,一是以该节点为端点向下走的最长路径,也就是图中的 1,二是经过该端点的最长路径,也就是 1 + 2(1是最长距离,2是次长距离)。

import java.util.Arrays;
import java.util.Scanner;

class Main{
	static int N = 10010;
	static int M = N * 2;//边数=点*2
	static int[] h = new int[N];
	static int[] e = new int[N];
	static int[] ne = new int[N];
	static int[] w = new int[N];
	static int idx;
	static int n;
	static int ans;
	
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		Arrays.fill(h, -1);
		for(int i = 0;i < n - 1;i++) {
			int a = sc.nextInt();
			int b = sc.nextInt();
			int c = sc.nextInt();
			add(a, b, c);
			add(b, a, c);
		}
		sc.close();
		
		dfs(1, -1);//一开始没有父节点,置为-1
		System.out.println(ans);
	}

	private static int dfs(int u, int father) {
		int dist = 0;//表示从当前点往下走的最大长度
		int d1 = 0, 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;
			}
		}
		ans = Math.max(ans, d1 + d2);
		return dist;
	}

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

例题:1073. 树的中心
第一章动态规划(十一)
思路:求出每个点距离其他点的最远距离,取最小值即为答案。

import java.util.Arrays;
import java.util.Scanner;

class Main{
	static int INF = Integer.MAX_VALUE >> 1;
	static int N = 10010;
	static int M = N * 2;//边数=点*2
	static int[] h = new int[N];
	static int[] e = new int[N];
	static int[] ne = new int[N];
	static int[] w = new int[N];
	static int idx;
	static int[] d1 = new int[N];//往下走的最长距离
	static int[] d2 = new int[N];//次长距离
	static int[] up = new int[N];//往上走的最长距离
	static int[] p1 = new int[N];
	static int[] p2 = new int[N];
	static int n;
	
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		Arrays.fill(h, -1);
		for(int i = 0;i < n - 1;i++) {
			int a = sc.nextInt();
			int b = sc.nextInt();
			int c = sc.nextInt();
			add(a, b, c);
			add(b, a, c);
		}
		sc.close();
		
		dfs_d(1, -1);//一开始没有父节点,置为-1
		dfs_u(1, -1);
		int ans = INF;
		for (int i = 1; i <= n; i++) {
			ans = Math.min(ans, Math.max(d1[i], up[i]));
		}
		System.out.println(ans);
	}

	private static int dfs_d(int u, int father) {//往下走的最长路径
		d1[u] = -INF;
		d2[u] = -INF;
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (j == father) continue;
			int d = dfs_d(j, u) + w[i];
			if (d >= d1[u]) {
				d2[u] = d1[u];
				d1[u] = d;
				p2[u] = p1[u];
				p1[u] = j;
			}else if (d > d2[u]) {
				d2[u] = d;
				p2[u] = j;
			}
		}
		if (d1[u] == -INF) {
			d1[u] = 0;
			d2[u] = 0;
		}
		return d1[u];
	}
	
	private static void dfs_u(int u, int father) {//往上走的最长路径
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (j == father) continue;
			if (p1[u] == j) {
				up[j] = Math.max(up[u], d2[u]) + w[i];
			}else {
				up[j] = Math.max(up[u], d1[u]) + w[i];
			}
			dfs_u(j, u);
		}
	}

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

例题:数字转换
【题目描述】
如果一个数 x的约数和 y (不包括他本身)比他本身小,那么 x 可以变成 y,y 也可以变成 x。例如 4 可以变为 3,1 可以变为 7。限定所有数字变换在不超过 n的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
【输入】
输入一个正整数 n。
【输出】
输出不断进行数字变换且不出现重复数字的最多变换步数。
【输入样例】
7
【输出样例】
3
【提示】
样例说明
一种方案为 4→3→1→7。
数据范围与提示:
对于 100% 的数据,1≤n≤50000。
————————————————————————————————————————————
对于每个 x,它的 y 是唯一的,所以我们将 y 看做 x 的父节点,本质是树的最长路径。

import java.util.Arrays;
import java.util.Scanner;

class Main{
	static int N = 50010;
	static int M = N * 2;//边数=点*2
	static int[] h = new int[N];
	static int[] e = new int[N];
	static int[] ne = new int[N];
	static int idx;
	static int n;
	static int[] sum = new int[N];//约数之和
	static boolean[] st = new boolean[N];//节点是否为根
	static int ans;
	
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		sc.close();
		
		for (int i = 1; i <= n; i++) {
			for (int j = 2; j <= n / i; j++) {
				sum[i * j] += i;
			}
		}
		Arrays.fill(h, -1);
		for (int i = 2; i <= n; i++) {//1的约数之和是0,不能枚举它
			if (i > sum[i]) {
				add(sum[i], i);
				st[i] = true;
			}
		}
		for (int i = 1; i <= n; i++) {
			if (!st[i]) {
				dfs(i);
			}
		}
		System.out.println(ans);
	}
	
	private 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;//无权,权重就是1
			if (d >= d1) {
				d2 = d1;
				d1 = d;
			}else if (d > d2) {
				d2 = d;
			}
		}
		ans = Math.max(ans, d1 + d2);
		return d1;
	}

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

例题:二叉苹果树
【题目描述】
有一棵二叉苹果树,如果数字有分叉,一定是分两叉,即没有只有一个儿子的节点。这棵树共 N个节点,标号 1 至 N,树根编号一定为 1。
我们用一根树枝两端连接的节点编号描述一根树枝的位置。一棵有四根树枝的苹果树,因为树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。
第一章动态规划(十一)
【输入】
第一行两个数 N和 Q ,N 表示树的节点数,Q表示要保留的树枝数量。
接下来 N−1行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。
【输出】
输出仅一行,表示最多能留住的苹果的数量。
【输入样例】
5 2
1 3 1
1 4 10
2 3 20
3 5 20
【输出样例】
21
【提示】
数据范围与提示:
对于 100% 的数据,1≤Q≤N≤100,N≠1,每根树枝上苹果不超过 30000 个。
—————————————————————————————————————————————————————
简化的有依赖的背包问题,想留下某个节点就必须留下它的父节点。
f[i][j]:以 i 为根的树中,选 j 条边的最大价值。
将每个子树看做一组物品

import java.util.Arrays;
import java.util.Scanner;

class Main{
	static int N = 110;
	static int M = N * 2;//边数=点*2
	static int[] h = new int[N];
	static int[] e = new int[M];
	static int[] w = new int[M];
	static int[] ne = new int[N];
	static int idx;
	static int n;
	static int m;
	static int[][] f = new int[N][N];
	
	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 = 0;i < n - 1;i++) {
			int a = sc.nextInt();
			int b = sc.nextInt();
			int c = sc.nextInt();
			add(a, b, c);
			add(b, a, c);
		}
		sc.close();
		
		dfs(1, -1);
		System.out.println(f[1][m]);
	}
	
	private static void dfs(int u, int father) {
		for (int i = h[u]; i != -1; i = ne[i]) {//枚举物品组
			if (e[i] == father) continue;
			dfs(e[i], u);
			for (int j = m; j >= 0; j--) {//枚举体积 大-->小
				for (int k = 0; k < j; k++) { //决策
					f[u][j] = Math.max(f[u][j], f[u][j - k - 1] + f[e[i]][k] + w[i]);
				}
			}
		}
	}

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

例题:323. 战略游戏
第一章动态规划(十一)
摘自–“小呆呆”
第一章动态规划(十一)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main{
    static int N = 1510;
    static int M = N;
    static int n;
    static int m;
    static int[][] f = new int[N][2];
    static int[] h = new int[N];
    static int[] e = new int[M];
    static int[] ne = new int[M];
    static int idx = 0;
    static boolean[] st = new boolean[N];
    
    private static void add(int a,int b) {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    private static void dfs(int u) {
        f[u][1] = 1;f[u][0] = 0;
        for(int i = h[u];i != -1;i = ne[i]) {
            int j = e[i];
            dfs(j);

            f[u][0] += f[j][1];
            f[u][1] += Math.min(f[j][1], f[j][0]);
        }
    }
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while(true) {
            String str = br.readLine();
            if (str == null) break;
            int n = Integer.parseInt(str);
            Arrays.fill(h, -1);
            Arrays.fill(st, false);
            idx = 0;
            while(n -- > 0) {
                String[] s = br.readLine().split(" ");
                int a = Integer.parseInt(s[0].substring(0, s[0].indexOf(':')));
                if(s.length > 1) {
                    for(int i = 1;i < s.length ;i ++) {
                        int b = Integer.parseInt(s[i]);
                        add(a,b);
                        st[b] = true;
                    }
                }
            }
            int root = 0;
            while(st[root]) root ++;
            dfs(root);
            System.out.println(Math.min(f[root][0], f[root][1]));
        }
    }
}

例题:皇宫看守
【题目描述】
太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状,某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。
帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。
第一章动态规划(十一)
【输入】
输入中数据描述一棵树,描述如下:
第一行 n,表示树中结点的数目。
第二行至第 n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号 i(0<i≤n),在该宫殿安置侍卫所需的经费 k,该边的儿子数 m,接下来 m 个数,分别是这个节点的 m 个儿子的标号r1,r2,⋯,rm。
对于一个 n个结点的树,结点标号在 1 到 n之间,且标号不重复。
【输出】
输出最少的经费
【输入样例】
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0
【输出样例】
25
【提示】
样例解释:
有六个区域被安排的情况如左图所示。
如右图,灰色点安排了警卫,2号警卫可以观察 1,2,5,6,3 号警卫可以观察 1,3,4 号警卫可以观察 1,4。
总费用:16+5+4=25
第一章动态规划(十一)
数据范围与提示:
对于 100% 的数据,0<n≤1500。
————————————————————————————————————————————————————
f[i][0] : 点 i 被父节点看到的所有摆放方案中的最小花费。
f[i][1] : 点 i 被子节点看到的所有摆放方案中的最小花费。
f[i][2]:在点 i 上放警卫的所有摆放方案的最小花费。

f[i][0] = 所有 min(f[j][1], f[j][2]) 的 和
f[i][2] = 所有 min(f[j][0], f[j][1], f[j][2]) 的 和
f[i][1] (需要枚举哪个子节点看到它) = 枚举k次 min{f[k][2] + 除k之外的所有 min(f[j][1], f[j][2]) 的和}

import java.util.Arrays;
import java.util.Scanner;

public class Main{
	static int N = 1510;
	static int n;
	static int[][] f = new int[N][3];
	static int[] h = new int[N];
	static int[] e = new int[N];
	static int[] w = new int[N];
	static int[] ne = new int[N];
	static int idx = 0;
	static boolean[] st = new boolean[N];

	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 id = sc.nextInt();
			int cost = sc.nextInt();
			int cnt = sc.nextInt();
			w[id] = cost;
			while(cnt-- > 0){
				int ver = sc.nextInt();
				add(id, ver);
				st[ver] = true;
			}
		}
		sc.close();
		
		int root = 1;
		while(st[root]) root++;
		dfs(root);
		System.out.println(Math.min(f[root][1], f[root][2]));
	}
	
	private static void dfs(int u) {
		f[u][2] = w[u];//花费w[u]
		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]);
			int tmp = Math.min(f[j][0], f[j][1]);
			f[u][2] += Math.min(tmp, f[j][2]);
		}
		f[u][1] = Integer.MAX_VALUE >> 1;
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			f[u][1] = Math.min(f[u][1], f[j][2] + f[u][0] - Math.min(f[j][1], f[j][2]));
		}
	}

	private static void add(int a,int b) {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
}
相关标签: 算法提高课