树形DP模型


树的最长路径

题目描述:

给定一棵树,树中包含 n n n 个结点(编号 1 1 1 ~ n n n )和 n − 1 n-1 n1 条无向边,每条边都有一个权值。

现在请你找到树中的一条最长路径。

换句话说,要找到一条路径,使得路径两端的点的距离最远。

注意:路径中可以只包含一个点。

输入格式:

第一行包含整数 n n n

接下来 n − 1 n-1 n1 行,每行包含三个整数 a i , b i , c i a_i , b_i , c_i ai,bi,ci ,表示点 a i a_i ai b i b_i bi 之间存在一条权值为 c i c_i ci 的边。

输出格式:

输出一个整数,表示树的最长路径的长度。

数据范围:

1 ≤ n ≤ 10000 1 ≤ n ≤ 10000 1n10000

1 ≤ a i , b i ≤ n 1 ≤ a_i, b_i ≤ n 1ai,bin

− 1 0 5 ≤ c i ≤ 1 0 5 -10^5 ≤ c_i ≤ 10^5 105ci105

输入样例:

6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7

输出样例:

22

代码实现

在这里插入图片描述

因此找次长和最长组成最长路径的答案

力扣相似题目

import sys
input = sys.stdin.readline

n = int(input().strip())
g = [[] for _ in range(n)]

for i in range(n - 1):
    a, b, c = map(int, input().strip().split())
    g[a - 1].append((b - 1, c))
    g[b - 1].append((a - 1, c))

ans = 0  # 记录最长路径答案


def dfs(x, fa):
    global ans
    max_val = 0  # 找子树最大链长
    for y, val in g[x]:
        if y == fa:  # 跳过祖先节点
            continue
        next_val = dfs(y, x) + val  # 当前枚举的子节点链长
        ans = max(max_val + next_val, ans)  # 将先前的最大链长 + 枚举的子节点链长
        max_val = max(max_val, next_val)  # 更新子树最大链长
    return max_val


dfs(0, -1)
print(ans)

*树的中心

题目描述:

给定一棵树,树中包含 n n n 个结点(编号 1 1 1 ~ n n n )和 n − 1 n-1 n1 条无向边,每条边都有一个权值。

请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。

输入格式:

第一行包含整数 n n n

接下来 n − 1 n-1 n1 行,每行包含三个整数 a i , b i , c i a_i , b_i , c_i ai,bi,ci,表示点 a i a_i ai b i b_i bi 之间存在一条权值为 c i c_i ci 的边。

输出格式:

输出一个整数,表示所求点到树中其他结点的最远距离。

数据范围:

1 ≤ n ≤ 10000 1 ≤ n ≤ 10000 1n10000

1 ≤ a i , b i ≤ n 1 ≤ a_i, b_i ≤ n 1ai,bin

1 ≤ c i ≤ 1 0 5 1 ≤ c_i ≤ 10^5 1ci105

输入样例:

5 
2 1 1 
3 2 1 
4 3 1 
5 1 1

输出样例:

2

换根dp的理解

换根dp的经典题目

大佬的题解

妙!
在这里插入图片描述


代码实现

import sys
input = sys.stdin.readline

n = int(input().strip())
g = [[] for _ in range(n)]

for i in range(n - 1):
    a, b, c = map(int, input().strip().split())
    g[a - 1].append((b - 1, c))
    g[b - 1].append((a - 1, c))

# ans   -记录当前节点的到其他节点的最远距离
# size  -记录当前节点的子节点数量
ans, size = [0] * n, [1] * n

# 计算 0 节点的到树中其他结点的最远距离
def dfs(x, fa):
    res = 0
    for y, val in g[x]:
        if y == fa:
            continue
        res += dfs(y, x) + val
        size[x] += size[y]
    ans[0] += res
    return res

dfs(0, -1)

# 通过 0 节点更新其他节点的到树中其他结点的最远距离(换根)
def reroot(x, fa):
    for y, val in g[x]:
        if y == fa:
            continue
        ans[y] = ans[x] + val * (n - 2 * size[y])
        reroot(y, x)


reroot(0, -1)
print(ans.index(min(ans)) + 1)

数字转换

题目描述:

如果一个数 x x x 的约数之和 y y y(不包括它本身)比它本身小,那么 x x x 可以变为 y y y y y y 也可以变为 x x x

例如, 4 4 4 可以变为 3 3 3 1 1 1 可以变为 7 7 7

限定所有数字变换在不超过 n n n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。

输入格式:

输入一个正整数 n n n

输出格式:

输出不断进行数字变换且不出现重复数字的最多变换步数。

数据范围:

1 ≤ n ≤ 50000 1 ≤ n ≤ 50000 1n50000

输入样例:

7

输出样例:

3

样例解释:
一种方案为: 4 → 3 → 1 → 7 4→3→1→7 4317


代码实现

以题目 x 与 y 的关系进行建图

再以 1 为起点找最大直径

为什么找 1 1 1,因为 1 1 1 可以将绝大部分数字串起来,最优解一定出现在含 1 1 1 的树上。

另外, 以 x x x y y y 关系建立的不是树,而是森林。例如 6 6 6 的约数之和还是为 6 6 6,故 6 6 6 单独在一个树上。

import sys
input = sys.stdin.readline

n = int(input().strip())
g = [[] for _ in range(n + 1)]

for i in range(2, n + 1):
    j, sum_val = 2, 1
    while j <= i // j:
        if i % j:
            j += 1
            continue
        if j * j == i:
            sum_val += j
        else:
            sum_val += j + i // j
        j += 1
    g[sum_val].append(i)
    g[i].append(sum_val)


ans = 0
def dfs(x, fa):
    global ans
    max_val = 0
    for y in g[x]:
        if y == fa:
            continue
        val = dfs(y, x) + 1
        ans = max(ans, max_val + val)
        max_val = max(max_val, val)

    return max_val


dfs(1, -1)
print(ans)

*二叉苹果树

题目描述:

有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的节点。

这棵树共 N N N 个节点,编号为 1 1 1 N N N ,树根编号一定为 1 1 1

我们用一根树枝两端连接的节点编号描述一根树枝的位置。

一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。

这里的保留是指最终与 1 1 1 号点连通。

输入格式:

第一行包含两个整数 N N N Q Q Q ,分别表示树的节点数以及要保留的树枝数量。

接下来 N − 1 N−1 N1 行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。

输出格式:

输出仅一行,表示最多能留住的苹果的数量。

数据范围:

1 ≤ Q < N ≤ 100 1 ≤ Q < N ≤ 100 1Q<N100 N ≠ 1 N ≠ 1 N=1 ,每根树枝上苹果不超过 30000 30000 30000 个。

输入样例:

5 2
1 3 1
1 4 10
2 3 20
3 5 20

输出样例:

21

代码实现

跟背包问题DP模型中的有依赖的背包问题同样处理

import sys
input = sys.stdin.readline

n, q = map(int, input().strip().split())
g = [[] for _ in range(n)]

for _ in range(n - 1):
    a, b, c = map(int, input().strip().split())
    g[a - 1].append((b - 1, c))
    g[b - 1].append((a - 1, c))

f = [[0] * (q + 1) for _ in range(n)]


def dfs(x, fa):
    for y, val in g[x]:
        if y == fa:
            continue
        dfs(y, x)
        
        # 由于子树遍历
        # 逆向更新防止更新两次
        for i in range(q, 0, -1):
            for j in range(i):
                f[x][i] = max(f[x][i], f[x][i - 1 - j] + f[y][j] + val)


dfs(0, -1)
print(f[0][-1])

*保安站岗

题目描述:

五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序。

已知整个地下超市的所有通道呈一棵树的形状;某些通道之间可以互相望见。总经理要求所有通道的每个端点(树的顶点)都要有人全天候看守,在不同的通道端点安排保安所需的费用不同。

一个保安一旦站在某个通道的其中一个端点,那么他除了能看守住他所站的那个端点,也能看到这个通道的另一个端点,所以一个保安可能同时能看守住多个端点(树的结点),因此没有必要在每个通道的端点都安排保安。

编程任务:

请你帮助超市经理策划安排,在能看守全部通道端点的前提下,使得花费的经费最少。

输入格式:

1 1 1 n n n,表示树中结点的数目。

2 2 2 行至第 n + 1 n+1 n+1 行,每行描述每个通道端点的信息,依次为:该结点标号 i i i 0 < i ≤ n 0<i \le n 0<in),在该结点安置保安所需的经费 k k k ≤ 10000 ≤10000 10000),该边的儿子数 m m m,接下来 m m m 个数,分别是这个节点的 m m m 个儿子的标号 r 1 , r 2 , ⋯   , r m r_1,r_2,\cdots, r_m r1,r2,,rm

对于一个 n n n 0 < n ≤ 1500 0<n \le 1500 0<n1500)个结点的树,结点标号在 1 1 1 n n n 之间,且标号不重复。

输出格式:

输出一行一个整数,表示最少的经费。

样例 #1

样例输入 #1

6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0

样例输出 #1

25

提示

样例解释

在结点 2 , 3 , 4 2,3,4 2,3,4 安置 3 3 3 个保安能看守所有的 6 6 6 个结点,需要的经费最小: 25 25 25


代码实现

import sys
from math import inf
sys.setrecursionlimit(1000000)
input = sys.stdin.readline


n = int(input().strip())
g = [[] for _ in range(n + 1)]
cost = [0] * (n + 1)

for _ in range(n):
    nums = list(map(int, input().strip().split()))
    a = nums[0]
    cost[a] = nums[1]
    for b in nums[3:]:
        g[a].append(b)
        g[b].append(a)


def dfs(x, fa):
    choose, by_fa, by_children = cost[x], 0, inf
    for y in g[x]:
        if y == fa:
            continue
        y_choose, y_by_fa, y_by_children = dfs(y, x)
        choose += min(y_choose, y_by_fa)
        by_fa += min(y_choose, y_by_children)
        by_children = min(by_children, y_choose - y_by_children)
    by_children = max(0, by_children) + by_fa
    return choose, by_fa, by_children


choose, _, by_children = dfs(1, -1)
print(min(choose, by_children))

扩展练习

监控二叉树

二叉树灯饰

二叉树染色


战略游戏

题目背景:

Bob 喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。

题目描述:

他要建立一个古城堡,城堡中的路形成一棵无根树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能瞭望到所有的路。

注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被瞭望到。

请你编一程序,给定一树,帮 Bob 计算出他需要放置最少的士兵。

输入格式:

第一行一个整数 n n n,表示树中结点的数目。

第二行至第 n + 1 n+1 n+1 行,每行描述每个结点信息,依次为:一个整数 i i i,代表该结点标号,一个自然数 k k k,代表后面有 k k k 条无向边与结点 i i i 相连。接下来 k k k 个整数,分别是每条边的另一个结点标号 r 1 , r 2 , ⋯   , r k r_1,r_2,\cdots,r_k r1,r2,,rk,表示 i i i 与这些点间各有一条无向边相连。

对于一个 n n n 个结点的树,结点标号在 0 0 0 n − 1 n-1 n1 之间,在输入数据中每条边只出现一次。保证输入是一棵树。

输出格式:

输出文件仅包含一个整数,为所求的最少的士兵数目。

样例 #1

样例输入 #1

4
0 1 1
1 2 2 3
2 0
3 0

样例输出 #1

1

提示:

数据规模与约定

对于全部的测试点,保证 1 ≤ n ≤ 1500 1 \leq n \leq 1500 1n1500


代码实现

与保安站岗不同的是,战略游戏中由于是看边关系。父节点监控不到子节点与孙子节点之间连的边,因此只需要两个自变量进行即可。

import sys
sys.setrecursionlimit(1000000)
input = sys.stdin.readline


n = int(input().strip())
g = [[] for _ in range(n)]

for _ in range(n):
    nums = list(map(int, input().strip().split()))
    a = nums[0]
    for b in nums[2:]:
        g[a].append(b)
        g[b].append(a)


def dfs(x, fa):
    choose, no_choose = 1, 0
    for y in g[x]:
        if y == fa:
            continue
        y_choose, y_no_choose = dfs(y, x)
        choose += min(y_choose, y_no_choose)
        no_choose += y_choose

    return choose, no_choose

print(min(dfs(0, -1)))

皇宫看守

题目描述:

太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。

皇宫各个宫殿的分布,呈一棵树的形状,宫殿可视为树中节点,两个宫殿之间如果存在道路直接相连,则该道路视为树中的一条边。

已知,在一个宫殿镇守的守卫不仅能够观察到本宫殿的状况,还能观察到与该宫殿直接存在道路相连的其他宫殿的状况。

大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。

可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

输入格式:

输入中数据描述一棵树,描述如下:

第一行 n n n,表示树中节点的数目。

接下来 n n n 行,每行描述每个宫殿节点信息,依次为:该宫殿节点标号 i i i,在该宫殿安置侍卫所需的经费 k k k,该节点的子节点数 m m m,接下来 m m m 个数,分别是这个节点的 m m m 个子节点的标号 r 1 , r 2 , … , r m r_1, r_2, …, r_m r1,r2,,rm

对于一个 n n n 个节点的树,节点标号在 1 到 n 之间,且标号不重复。

输出格式:

输出一个整数,表示最少的经费。

数据范围:

1 ≤ n ≤ 1500 1 ≤ n ≤ 1500 1n1500

输入样例:

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

代码实现

与保安站岗同一做法

import sys
from math import inf
input = sys.stdin.readline

n = int(input().strip())
g = [[] for _ in range(n)]
cost = [0] * n

for _ in range(n):
    line = list(map(int, input().strip().split()))
    a = line[0]
    cost[a - 1] = line[1]
    for b in line[3:]:
        g[a - 1].append(b - 1)
        g[b - 1].append(a - 1)


def dfs(x, fa):
    choose, by_fa, by_son = cost[x], 0, inf
    for y in g[x]:
        if y == fa:
            continue
        y_choose, y_by_fa, y_by_son = dfs(y, x)
        choose += min(y_choose, y_by_fa)
        by_fa += min(y_choose, y_by_son)
        by_son = min(by_son, y_choose - y_by_son)
    by_son = max(0, by_son) + by_fa
    return choose, by_fa, by_son

choose, _, by_son = dfs(0, -1)
print(min(choose, by_son))

相关推荐

  1. 信号塔(树形dp

    2024-04-07 00:00:02       33 阅读
  2. 数字转换(树形DP

    2024-04-07 00:00:02       24 阅读
  3. 动态规划-树形DP入门-自上而下树形DP

    2024-04-07 00:00:02       59 阅读
  4. C. Infected Tree -树形dp

    2024-04-07 00:00:02       65 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-07 00:00:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-07 00:00:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-07 00:00:02       82 阅读
  4. Python语言-面向对象

    2024-04-07 00:00:02       91 阅读

热门阅读

  1. Qt Remote Objects (QtRO) 笔记

    2024-04-07 00:00:02       37 阅读
  2. 微信小程序开发中的消息订阅与模板消息发送

    2024-04-07 00:00:02       40 阅读
  3. 三足鼎立 PTA(25分)

    2024-04-07 00:00:02       81 阅读
  4. 【DevOps工具篇】Keycloak安装配置及脚本化

    2024-04-07 00:00:02       36 阅读
  5. composer常见错误解决

    2024-04-07 00:00:02       36 阅读
  6. Docker in Docker原理与实战

    2024-04-07 00:00:02       35 阅读