动态规划之树型dp

树形Dp计算树上的dp,就是在树的结构上运用dp思想。
而树形dp大多用的是递归,因为对于树的节点,深度复杂,不太能用for循环去枚举状态。

https://www.luogu.com.cn/problem/P1352
一道树型dp模板题,简单了解树型dp。
题目就是上司和员工这样一层一层的树型结构。
我们可以选择上司,放弃员工,也可以选择员工,放弃上司。
这样我们就可以用dp[i][0/1]表示到第i个为止 选/不选 第i个所能获得的最大效益。
这样我们就枚举每个节点的子节点(每个上司的员工),转移方程就是:
f[i][0]+=max(f[j][1],f[j][0]); 不选第i个点
f[i][1]+=f[j][0]; 选第i个点

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <bits/stdc++.h>
#define ll long long
#define MAX 6010 
using namespace std;
vector<int> child[MAX];
int visit[MAX];		用来记录子节点,用于后面寻找根节点
int a[MAX],f[MAX][2];
void dfs(int i){
	f[i][0]=0;
	f[i][1]=a[i-1];
	//枚举子节点
	for(int k=0;k<child[i].size();k++){
		int y=child[i][k];
		dfs(y);
		f[i][0]+=max(f[y][1],f[y][0]) ;
		f[i][1]+=f[y][0];
	}
}
int main()
{
	std::ios::sync_with_stdio(false);
	int n,x,y;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	for(int i=0;i<n-1;i++){
		scanf("%d%d",&x,&y);
		child[y].push_back(x);
		visit[x]=1;
	}
	int root;
	for(int i=1;i<=n;i++)
		if(!visit[i]){
			root=i;
			break;
		}
	dfs(root);
	printf("%d\n",max(f[root][0],f[root][1]));
}



https://www.luogu.com.cn/problem/P2015
题目要求 q个树枝下能最多有多少个苹果。二叉苹果树,很明显的树型。就是要选取一些树枝求最多苹果。当然树枝是要连在一起的,中途不能断开。这样我们就能转换为求左子树最多和右子树最多。
这样我们要维护的值就是 当前的节点 i 和 j 个树枝
dp[i][j]表示根节点为i且树枝为j的最多苹果。
转移方程就为:
dp[ i ][ j ]=max(dp[ i ][ j ],dp[ i ][j-k-1]+dp[ y ][ k ] +val[ i ][ y ] )
就是求i为根节点 转为求 y为根节点 有k个树枝+i为根节点,有j-k-1个树枝(这里减一是因为y和i中间还连了一根)+ i 和 y这条边的苹果数量

题目中没有给出节点的父子关系,所以我们用双向数组存。

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,q;
int val[110][110],visit[110],f[110][110];
vector<int> child[110];
void dfs(int x){
	visit[x]=1;
	for(int i=0;i<child[x].size();i++){
		int y=child[x][i];
		//用双向数组存的,所以里面可能有父节点
		if(visit[y]==1) continue;
		visit[y]=1;
		dfs(y);
		//枚举树枝的个数
		for(int m=q;m>0;m--)
			//枚举字节点的树枝的个数
			for(int k=m-1;k>=0;k--)
				f[x][m]=max(f[x][m],f[y][k]+f[x][m-k-1]+val[x][y]);
	}
}
int main()
{
	std::ios::sync_with_stdio(false);
	scanf("%d%d",&n,&q);
	int x,y,z;
	for(int i=0;i<n-1;i++) {
		scanf("%d%d%d",&x,&y,&z);
		val[x][y]=z;
		val[y][x]=z;
		child[x].push_back(y);
		child[y].push_back(x);
	}
	dfs(1);
	printf("%d\n",f[1][q]);
}



https://www.luogu.com.cn/problem/P3177
dp[ i ][ j ]表示以i为根节点,j个黑点个数对结果的贡献
最主要的是两两之间的距离怎么求。
我们将其装换为两两的路径和,然后我们考虑每一条边的贡献。
如果两个黑点在边的同侧,那么对结果无贡献,如果在两侧,则这条边必经过两次。
如这条边左边有两个黑点,右边两个黑点,则算这四个黑点两两的距离则
算这条边对其贡献度为 2*2 *v=4 * v
那么一条边的贡献度为这条边的权值 * (这条边两侧的黑点乘积+白点乘积)

邻接表存储树https://blog.csdn.net/xiang_yu_pai/article/details/104642609

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int size=2020;
ll dp[size][size];
int head[size],dig[size];		//dig[i]表示以 i 为根点的子树的节点数量
int cnt,N,K;
struct node{
	ll y,nex,v;
}e[size<<1+1];
void add(int a,int b,int w){  //邻接表存储树
	e[++cnt].y=b;
	e[cnt].v=w;
	e[cnt].nex=head[a];
	head[a]=cnt;
}
void dfs(int x,int father){
	dig[x]=1;
	dp[x][0]=0,dp[x][1]=0;	//不管黑点为0或1,都是合法的情况
	for(int i=head[x];i!=0;i=e[i].nex){
		int y=e[i].y;
		if(y==father) continue;	//双向存储,有包含父节点的情况,过掉
		dfs(y,x);
		dig[x]+=dig[y];
		for(int j=min(K,dig[x]);j>=0;j--){		//x的子树最多有dig[x]个节点
			for(int k=0;k<=min(j,dig[y]);k++){	//y的子树最多有dig[y]个节点
				if(dp[x][j-k]==-1) continue;    //不合法的情况
				ll t=e[i].v*( (K-k)*k + (dig[y]-k)*(N-dig[y]+k-K));
				dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]+t);
			}
		}
	}
}
int main()
{
	int a,b,c;
	scanf("%d%d",&N,&K);
	memset(dp,-1,sizeof(dp));
	for(int i=1;i<N;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);				//不知道节点的父子关系,所以用双向存储
		add(b,a,c);
	}
	dfs(1,0);
	printf("%lld\n",dp[1][K]);
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值