[算法]树形dp

关键词:树形 \(dp\) 概念与应用,树形背包,换根dp

树形 \(dp\) 是一种在树上跑 \(dp\) 的算法

在这我们引用 \(\_Lancy\) 大佬的博客中两段话来讲解树形 \(dp\) 概念,那就是
"树形 \(dp\) 是一种很优美的动态规划,真的很优美真的,前提是在你学会它之后。"
"树形 \(dp\) 的主要实现形式是 \(dfs\) ,在 \(dfs\)\(dp...\) "

例题1简化题目就是选节点使权值之和最大,并且选的节点其父节点不能选

本题对于一个节点u有两种选择
1.选择该节点
所以其子节点不能选择 \(f[u][1] = a[u] + \sum_{v \in u} f[v][0]\)
2.不选择该节点
所以子节点可以选择 \(f[u][0] = \sum_{v \in u} \max(f[v][0],f[v][1])\)

#include <bits/stdc++.h>
using namespace std;
const int N = 6e3+10;
int n,a[N],f[N][2];
bool vis[N];
vector<int> g[N];
void dfs(int u){
    f[u][0]=0;
    f[u][1]=a[u];
    for(int v:g[u]){
        dfs(v);
        f[u][0]+=max(f[v][1],f[v][0]);
        f[u][1]+=f[v][0];
    }
}
int main(){

    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<n;i++){
        int k,l;
        cin>>l>>k;
        g[k].push_back(l);
        vis[l]=1;
    }
    int root=1;
    for(int i=1;i<=n;i++){
        if(!vis[i])root=i;
    }
    dfs(root);
    cout<<max(f[root][0],f[root][1])<<"\n";

    return 0;
}

例题2:简化题目,有一颗树,选择 \(m+1\) 个节点,并且每个节点选择前提是从该节点到根节点的路径上所有节点必须全部选,最后询问选 \(m+1\) 个节点权值之和的最大值

本题选择节点要满足的条件可以联想到有依赖背包 \(dp\)

#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int n, m, a[N];
vector<int> g[N];
int f[N][N];
void dfs(int u) {
    f[u][0]=0;
    f[u][1]=a[u];
    for(int v:g[u]){
        dfs(v);
        for(int j=m+1;j>=1;j--){
            for(int k=0;k<j;k++){
                f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
            }
        }
    }
}
int main() {

    cin >> n >> m;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x>>a[i];
        g[x].push_back(i);
    }
    memset(f,0xcf,sizeof(f));
    dfs(0);
    cout<<f[0][m+1]<<"\n";

    return 0;
}

例题3:化简题目:求出子树权值之和的最大值

设f[u]为以u为根的子树权值之和最大值
针对每个子节点如果子节点v的f[v]为负数说明没有考虑的必要了
所以每一个节点u可以写成 \(f[u] = \sum_{v \in u} max(0,f[v])\)
由于不知道根是谁,所以需要遍历一遍,若所有值都小于mx(单个节点最大值)就输出mx当作答案

#include <bits/stdc++.h>
using namespace std;
const int N=16100;
int a[N],f[N],mx=-2e9;
vector<int> g[N];
void dfs(int u,int fa){
    f[u]=a[u];
    for(int v:g[u]){
        if(v==fa) continue;
        dfs(v,u);
        f[u]+=max(0,f[v]);
    }
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        mx=max(mx,a[i]);
    }
    for(int i=1;i<n;i++){
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(1,0);
    int ans=-2e9;
    for(int i=1;i<=n;i++){
        ans=max(ans,f[i]);
    }
    if(ans>mx)cout << ans << endl;
    else cout<<mx;
    return 0;
}

例题4:简化题目,求树上最长的不重复经过一个点的路径长度是多少

本题也就是求出根节点到最深的叶子节点的深度,由于不知道根节点是多少,我们可以采取的策略就是先假设1为根节点再找谁的深度最大,把他当作根节点再dfs一遍

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n,d[N],c;
vector<int> g[N];
void dfs(int u,int fa){
    for(auto v:g[u]){
        if(v==fa)continue;
        d[v]=d[u]+1;
        if(d[v]>d[c])c=v;
        dfs(v,u);
    }
}
int main(){

    cin>>n;
    for(int i=2;i<=n;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1,1);
    d[c]=0;
    dfs(c,0);
    cout<<d[c];

    return 0;
}
posted @ 2026-02-18 19:26  rdrd  阅读(13)  评论(0)    收藏  举报