20251109 树状DP总结

引子

树状DP,就是在树上进行动态规划,由于树的特殊递归属性,所以树状DP都是在树上进行的。

树状DP可以分成两种:

  1. 兄弟节点间无相互约束
  2. 兄弟节点间有相互约束

第一种,兄弟节点间无相互约束的树状DP,这种树状DP意味着兄弟节点之间互不干扰,你干你的,他干他的。这种树状DP的经典例题有 洛谷 没有上司的舞会。

第二种,兄弟节点间有相互约束的树状DP,这种树状DP意味着兄弟节点间会互相干扰,就像给这几个兄弟节点分苹果,你分得多一点别人就分的少一点,你分得少一点别人就分的多一点。这种树状DP的经典例题有 洛谷 二叉苹果树。

P1352 没有上司的舞会

题目传送门

简化题意:有 n 个点,他们的从属关系用一颗树来表示,父结点是子结点的直接上司。每个点有一个点权,求拿出若干个点且这若干个点的任意两点无从属关系的最大点权和。

我们定义状态 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1] 表示以节点𝑖为根的子树的最大点权值。其中:

  • 第二维取0表示节点 i 不参加舞会
  • 第二维取1表示节点 i 参加舞会

状态转移方程如下(设 v 为 x 的子节点):

  1. 当 x 不参加舞会时:
    d p [ x ] [ 0 ] + = m a x ( d p [ v ] [ 1 ] , d p [ v ] [ 0 ] ) ; dp[x][0] += max(dp[v][1],dp[v][0]); dp[x][0]+=max(dp[v][1],dp[v][0]);
    即子节点可以自由选择是否参加
  2. 当 x 参加舞会时:
    d p [ x ] [ 1 ] + = d p [ v ] [ 0 ] ; dp[x][1] += dp[v][0]; dp[x][1]+=dp[v][0];
    此时所有子节点都不能参加

我们用DFS遍历这棵树,在回溯时更新每个节点的DP。

#include<bits/stdc++.h>
using namespace std;
int r[6005],dp[6005][2];
vector<int> E[6005];
void dfs(int x,int fa){
	dp[x][1]=r[x];
	for(int i=0;i<E[x].size();i++){
		int v=E[x][i];
		if(v==fa)continue;
		dfs(v,x);
		dp[x][0]+=max(dp[v][1],dp[v][0]);
		dp[x][1]+=dp[v][0];
	}
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>r[i];
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		E[u].push_back(v);
		E[v].push_back(u);
	}
	dfs(1,0);
	cout<<max(dp[1][0],dp[1][1]);
	return 0;
}

P2015 二叉苹果树

题目传送门

题意:给定一棵树,n个点,有边权,至少保留q条边,求最大边权和。

1.q条边分到左子树多,那么右子树就少,兄弟节点间有数量约束,考虑第二类树型DP。

2.状态:dp[i][j] 表示以 i 为根节点的子树保留至多 j 条边的最大边权和。

3.答案: dp[1][q]

4.状态转移:

int v=E[x][i].v,w=E[x][i].w;
if(v==fa)continue;
dfs(v,x);
sz[x]+=sz[v];
for(int j=min(q,sz[x]);j>=0;j--){
	for(int k=0;k<=min(j-1,sz[v]);k++){
		dp[x][j]=max(dp[x][j],dp[v][k]+dp[x][j-k-1]+w);
	}
}

5.初始状态: d p [ x ] [ 0 ] = 0 dp[x][0]=0 dp[x][0]=0

#include<bits/stdc++.h>
using namespace std;
struct node{
	int u,v,w;
};
vector<node> E[105];
int n,q,sz[105],dp[105][105];
void dfs(int x,int fa){
	sz[x]=1;
	for(int i=0;i<E[x].size();i++){
		int v=E[x][i].v,w=E[x][i].w;
		if(v==fa)continue;
		dfs(v,x);
		sz[x]+=sz[v];
		for(int j=min(q,sz[x]);j>=0;j--){
			for(int k=0;k<=min(j-1,sz[v]);k++){
				dp[x][j]=max(dp[x][j],dp[v][k]+dp[x][j-k-1]+w);
			}
		}
	}
}
int main(){
	cin>>n>>q;
	for(int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		E[u].push_back({u,v,w});
		E[v].push_back({v,u,w});
	}
	dfs(1,0);
	cout<<dp[1][q];
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值