洛谷 P14919:[GESP202512 六级] 路径覆盖 ← 树形DP

【题目来源】
https://www.luogu.com.cn/problem/P14919

【题目描述】
给定一棵有 n 结点的有根树 T,结点依次以 1,2,…,n 编号,根结点编号为 1。方便起见,编号为 i 的结点称为结点 i。
初始时 T 中的结点均为白色。你需要将 T 中的若干个结点染为黑色,使得所有叶子到根的路径上至少有一个黑色结点。将结点 i 染为黑色需要代价 ci,你需要在满足以上条件的情况下,最小化染色代价之和。
叶子是指 T 中没有子结点的结点。

【输入格式】
第一行,一个正整数 n,表示结点数量。
第二行,n-1 个正整数 f2,f3,…,fn,其中 fi 表示结点 i 的父结点的编号,保证 fi<i。
第三行,n 个正整数 c1,c2,…,cn,其中 ci 表示将结点 i 染为黑色所需的代价。

【输出格式】
一行,一个整数,表示在满足所有叶子到根的路径上至少有一个黑色结点的前提下,染色代价之和的最小值。

【输入样例一】
4
1 2 3
5 6 2 3

【输出样例一】
2

【输入样例二】
7
1 1 2 2 3 3
64 16 15 4 3 2 1

【输出样例二】
10

【数据范围】
对于 40% 的测试点,保证2≤n≤16。
对于另外 20% 的测试点,保证 fi=i-1。
对于所有测试点,保证2≤n≤10^5,1≤ci≤10^9。

【算法分析】
● 动态规划的状态定义:f[i] 表示以节点 i 为根的子树满足“所有叶子到 i 的路径至少有一个黑色结点”的最小染色代价

● 遍历方式:由于题目中父结点编号一定小于子结点编号fi < i),因此从 n 到 1 逆序遍历,等价于从叶子结点到根结点的后序遍历(Postorder Traversal),能保证处理父结点时所有子结点已处理完毕。

● 状态转移逻辑

for(int i=n; i>=1; i--) {
    if(f[i]==0 || f[i]>c[i]) {
        f[i]=c[i];
    }
    f[fa[i]]+=f[i];
}

若 i 是叶子结点(无任何子结点),必须将 i 染黑,此时 f[i]=c[i]。这是因为叶子结点无子结点,无法通过子结点覆盖自身。所以,必须直接染黑叶子结点以满足覆盖要求。
若 i 是非叶子结点:有两种选择。
(1)选择自己染黑:代价为 c[i];
(2)选择子节点的覆盖代价之和:代价为所有子结点 f 值的总和;
取两种选择的最小值作为 f[i]。
这种设计,能够确保所有叶子到根的路径上至少有一个黑色结点
最终答案:根结点 1 的 f[1] 即为整棵树的最小覆盖代价。

【算法代码】

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;
int fa[maxn];
int c[maxn]; //c[i] stores cost of coloring node i black
long long f[maxn]; //f[i]:minimum covering cost of subtree rooted at node i
int n;

int main() {
    cin>>n;
    for(int i=2; i<=n; i++) {
        cin>>fa[i];
    }

    for(int i=1; i<=n; i++) {
        cin>>c[i];
    }

    for(int i=n; i>=1; i--) {
        if(f[i]==0 || f[i]>c[i]) {
            f[i]=c[i];
        }
        f[fa[i]]+=f[i];
    }

    cout<<f[1];

    return 0;
}

/*
in:
7
1 1 2 2 3 3
64 16 15 4 3 2 1

out:
10
*/





【参考文献】
https://mp.weixin.qq.com/s/QR-d0-pMi1M-YpQg3bTqpA
https://www.luogu.com.cn/problem/solution/P14919
https://blog.csdn.net/qq_36230375/article/details/156603829


 

posted @ 2026-01-12 13:52  Triwa  阅读(288)  评论(0)    收藏  举报