算法:树形dp

什么是树形dp?

树形dp是一种应用于树形结构的动态规划算法。在树形结构中,每个节点可以有零个或多个子节点,但没有环(即无向图中没有回路)。树形动态规划通常用于解决与树相关的问题,例如路径问题、最大独立集、最小路径覆盖等。

选择节点类

例1洛谷p1352

题意:有n个职员,每邀请到一个职员会增加快乐指数ri,如果某个职员的直接上司来参加了,那么这个职员就不来了。求最大快乐指数。

分析:

假设1号来了,所求的最大值就是2号没来的最大值加3号没来时的最大值最后加上1的权值;
假设1号没来,所求最大值就是max(2号来,2号不来)+max(3号来,3号不来);就得到了节点1的最大值,所有节点都可以根据这个方程求得该状态的最大值;主要的实现形式是dp[i][j][0/1],i是以i为根的子树,j是表示在以i为根的子树中选择𝑗j个子节点,0表示这个节点不选,1表示选择这个节点。有的时候j或0/1这一维可以压掉

设f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值

f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值

#include<bits/stdc++.h>
using namespace std;
const int N=6e3+10;
int a[N],ne[N],edge[N],dp[N][2],last[N],cnt=1;
bool yf[N];
void add(int a,int b){
	edge[cnt]=b;
	ne[cnt]=last[a];
	last[a]=cnt++;
}
void dfs(int x){
	dp[x][0]=0;//不叫x去
	dp[x][1]=a[x];//叫了x去
	for(int i=last[x];i>=1;i=ne[i]){//遍历x之后的所有子节点
		int j=edge[i];//j是i的直接子节点
		dfs(j);
		dp[x][0]+=max(dp[j][0],dp[j][1]);
		dp[x][1]+=dp[j][0];
	}
}
int main(){
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	int x,y;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		add(y,x);//y是x的父亲
		yf[x]=true;//x有父亲
	}
	
	int v=1;
	while(yf[v])v++;//找到没有父亲的点
	dfs(v);
	cout<<max(dp[v][1],dp[v][0])<<endl;
}
//for(int i=1;i<=7;i++){
//		cout<<edge[i]<<" "<<ne[i]<<" "<<last[i]<<endl;
//	}
//1 0 0
//2 1 0
//6 0 2
//7 3 4
//4 0 6
//3 5 0
//0 0 0

树形背包类

例2洛谷p2014

题意:有n门课程,要从中学m门课程,使得获得总学分最大化。并且在学某些课程之前要先学完某些先修课。求最大总学分。

分析:用dfs扫描整个数,然后对树上每个节点进行背包。先让当前节点加上父节点的权值,对于剩下可以选的几次,更新节点状态,调用dfs进行搜索,然后再更新父节点的最大权值。

#include<bits/stdc++.h>
using namespace std;
int val[1000],edge[1000],ne[1000],last[1000],f[1000][1000],cnt=1;
void add(int a,int b){
	edge[cnt]=b;
	ne[cnt]=last[a];
	last[a]=cnt++;
}
void dfs(int x,int y){
	if(y==0)return;//已经选了m个了
	for(int i=last[x];i>=1;i=ne[i]){
		int j=edge[i];//j是i的儿子;
		f[j][0]=f[x][0]+val[j];//初始状态
		for(int k=1;k<=y;k++){//将权值加上之前的值
			f[j][k]=f[x][k]+val[j];
		}
		dfs(j,y-1);//进入下一层搜索
		for(int k=1;k<=y;k++){//搜完啦可以更新了
			f[x][k]=max(f[x][k],f[j][k-1]);
		}
	}
}
int main(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++){
		int a;cin>>a>>val[i];
		add(a,i);//a是i的父亲节点
	}
	dfs(0,m);
	cout<<f[0][m];
}

例题

例3洛谷p2016

题意:所有士兵放在一棵树上,在这棵树的结点上放置最少数目的士兵,使得这些士兵能瞭望到所有的路。某个士兵在一个结点上时,与该结点相连的所有边将都可以被瞭望到。求最少的士兵数

分析:求最少士兵数,判断此题为选择节点类。判断其中一个士兵的状态

dp[x][0]+=dp[j][1];
dp[x][1]+=min(dp[j][1],dp[j][0])+1;

x为当前士兵,j为x的子节点。如果x不去,那它的子节点j就一定得去,如果x去,选择子节点去还是不去取最小值。

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int a[N],ne[N],edge[N],dp[N][4],last[N],cnt=1;
bool yf[N];
void add(int a,int b){
	edge[cnt]=b;
	ne[cnt]=last[a];
	last[a]=cnt++;
}
void dfs(int x,int fa){
	dp[x][0]=0;//x不去
	dp[x][1]=1;//x去
	for(int i=last[x];i;i=ne[i]){//遍历x之后的所有子节点
		int j=edge[i];//j是i的直接子节点
		if(j==fa)continue;//x没有儿子节点
		dfs(j,x);
		dp[x][0]+=dp[j][1];//不去就只能让儿子去了
		dp[x][1]+=min(dp[j][1],dp[j][0]);//去的话,看儿子去还是不去能取最小值
	}
}
int main(){
	int n;cin>>n;
	int x,k,y;
	for(int i=1;i<=n;i++){
		cin>>x>>k;
		x++;
		while(k--){
			cin>>y;
			y++;
			add(y,x);
			add(x,y);
		}	
	}
	dfs(1,0);
	cout<<min(dp[1][1],dp[1][0])<<endl;
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树形动态规划(Tree DP)是一种解决树状结构问题的算法思想。它利用了树这种特殊的数据结构的性质进行求解,常用来解决树的最优路径、最大值、最小值等类型的问题。 在夜深人静的时候写算法,我通常会采用以下步骤来完成树形dp的实现: 第一步是定义状态。我们首先需要确定问题的状态表示方式。对于树形dp来说,常用的状态表示方式是以节点为单位进行表示。我们可以定义dp[i]表示以节点i为根的子树的某种性质,比如最大路径和、最长路径长度等。 第二步是确定状态转移方程。根据问题的特点,我们需要找到状态之间的关系,从而确定状态转移方程。在树形dp中,转移方程常常与节点的子节点相关联。我们可以通过遍历节点的子节点,利用它们的状态来更新当前节点的状态,从而得到新的状态。 第三步是确定初始条件。在动态规划中,我们需要确定初始状态的值。对于树形dp来说,我们可以选择将叶节点作为初始状态,然后逐步向上更新,最终得到整棵树的最优解。 第四步是确定计算顺序。树形dp的计算通常是从根节点开始,自顶向下逐步计算,直到达到叶节点。因为树形dp的计算过程中需要利用到子节点的状态来更新当前节点的状态,所以必须按照计算顺序进行。 夜深人静时,写算法树形dp是相对较复杂的算法,需要仔细思考问题的状态表示方式,转移方程以及初始条件。在实现过程中,可以采用递归的方式进行代码编写,或者利用栈等数据结构进行迭代实现。 总的来说,夜深人静写算法树形dp需要耐心和细心,经过思考和实践,才能顺利解决树状结构问题。但是,一旦理解并掌握了树形dp的思想和方法,就能够高效地解决各种树形结构问题,提升算法的效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值