树形dp学习笔记

可能更好的阅读体验

1.算法描述

树形DP,即在树上进行的DP。(摘自OI Wiki树形 DP 部分

2.算法解析

首先,在正式开始dp(推f数组)之前,我们要先根据给出的条件建好树,并预处理出各个之间节点的关系,有时还要预处理出各个节点size(子树大小)等信息(另外,如果题中描述的一部分关系可能会形成森林,也许还要建虚根)

树形dp常用记忆化搜索来实现,从树的根节点开始,并通过子树中的节点的f数组进行状态转移,设置的dp状态也往往与节点的子树挂钩

例题

1.Luogu P1352 没有上司的舞会
参考文献:李煜东写的《算法竞赛进阶指南》一书
设 $dp_{i,0/1} idp Code $

#include<bits/stdc++.h>
using namespace std;
int n,r[6005],f[6005][2],l,k,s,fa[6005];
vector <int> son[6005];
int dfs(int x,int typ){
	if(!son[x].size()){
		if(typ)
			return f[x][1]=r[x];
		return f[x][0];
	}
	int g=son[x].size();
	if(typ){
		f[x][1]+=r[x];
		for(int i=0;i<g;++i)
			f[x][1]+=dfs(son[x][i],0);
		return f[x][1];
	}
	for(int i=0;i<g;++i)
			f[x][0]+=max(dfs(son[x][i],1),dfs(son[x][i],0));
	return f[x][0];
} 
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>r[i];
	for(int i=1;i<n;++i){
		cin>>l>>k;
		son[k].push_back(l);
		fa[l]=k;
	}
	for(int i=1;i<=n;i++){
		if(!fa[i]){
			s=i;
			break;
		}
	}
	cout<<max(dfs(s,0),dfs(s,1))<<endl;
	return 0;
}

2.Luogu P2015 二叉苹果树
参考文献(“子谦。”的一篇题解)
fi,j 为在 i 号节点的子树中保留j个树枝时留住的苹果的数量最大值,再写树上依赖背包即可。
Code

#include<bits/stdc++.h>
#define F(i,j,k) for(int i=j;i<=k;++i)
using namespace std;
int n,q,f[105][105],head[105],fa[105],size[105],ans,tmp,a,b,c;
bool vis[101];
struct edge{
    int u,v,w,next;
}s[202];
void addedge(int u,int v,int w){
    ++tmp;s[tmp].u=u,s[tmp].v=v,s[tmp].w=w,s[tmp].next=head[u];head[u]=tmp;
}
void dfs(int x){
    if(vis[x]) 
        return;
    for(int i=head[x];i;i=s[i].next){
        if(fa[x]!=s[i].v){
            fa[s[i].v]=x;
            dfs(s[i].v);
            size[x]=size[x]+size[s[i].v]+1;
            int y=s[i].v;
            for(int j=min(q,size[x]);j;--j){
                int r=min(q,size[y]);
                for(int k=r;k>=0;--k){//循环是先能加法就尽量不先做减法(如:能枚举剩下多少就不枚举删除多少) 
                	if(j-k-1>=0)
                    	f[x][j]=max(f[x][j],f[y][k]+f[x][j-k-1]+s[i].w);
                }
            }
        }
    }
    vis[x]=true;
}
int main(){
    cin>>n>>q;
    --n;
    F(i,1,n){
        cin>>a>>b>>c;
        addedge(a,b,c); 
        addedge(b,a,c);//由于题目没有规定是先输入父亲节点的编号还是先输入儿子节点的编号,所以这里要先建一条无向边,再在dfs中确认父子关系
    }
    ++n;
    dfs(1);
    //for(int i=1;i<=n;i++)	
    //	ans=max(ans,f[i][q]); //注意:在树中,如果没有祖先就不会有后代了 
    printf("%d\n",f[1][q]);  
    return 0;
}

3.Luogu P2014 [CTSC1997] 选课
参考文献1(“♞老姚♘”的一篇博客)
参考文献2:李煜东写的《算法竞赛进阶指南》一书
我此题与二叉苹果树这题的思路差不多 (特别说明:我建了一个虚根,其编号为0)
Code

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

int n,m,s[305],k[305],f[305][305],siz[305];
vector <int> son[305];

void dfs(int x){
    if(!son[x].size()){
        siz[x]=1;
        return;
    }
    int pp=son[x].size();
    for(int i=0;i<pp;++i){
        dfs(son[x][i]);
        siz[x]+=siz[son[x][i]];
    }
    ++siz[x];
}

void dfs1(int x){
	//cout<<x;
    int z=son[x].size();
    if(!z){
        f[x][1]=s[x];
        return;
    }
    if(x)
    	f[x][1]=s[x];
    int w=1;
    for(int i=0;i<z;++i){ 
        int q=son[x][i];
        dfs1(q);
        int t=siz[q];
        w+=t;
        for(int j=min(w,m);j;--j)
            for(int k=min(j-1,t);k;--k)
                f[x][j]=max(f[x][j],f[q][k]+f[x][j-k]);
    }
}

int main(){
    std::ios::sync_with_stdio(false);
    cin.tie();cout.tie();
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>k[i]>>s[i];
        son[k[i]].push_back(i);
    }
    dfs(0);
    for(int i=1;i<=n;++i)
    	if(!k[i])
    		dfs1(i);
	int z=son[0].size();
    int w=0;
    for(int i=0;i<z;++i){ 
        int q=son[0][i];
        int t=siz[q];
        w+=t;
        for(int j=min(w,m);j;--j)
            for(int k=min(t,j);k;--k)
                f[0][j]=max(f[0][j],f[q][k]+f[0][j-k]);
    }
    cout<<f[0][m];
    return 0;
}

本文可能会继续更改。

posted @   zhengjiaweiBKY  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示