树形DP

没有上司的舞会

没有上司的舞会

题意:一棵树上的每个结点都有一个值,每颗子树的根节点和其他节点不能同时被选,求可以选择的最大值

f[u][0] 表示所有从以u为根的子树中选择,并且不选u点的最大得分
f[u][1] 表示所有从以u为根的子树中选择,并且选u点的最大得分

对于节点u的子节点j:

        如果选u点,则f[u][1]累加所有不选子节点j的情况

        如果不选u点,则f[u][0]累加选择子节点j或不选子节点j中较大的一个

//dfs(root);//从根节点遍历 
void dfs(int u)
{
	f[u][1]=w[u];
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		dfs(j);
		f[u][0]+=max(f[j][0],f[j][1]);
		f[u][1]+=f[j][0];
	}
} 

树的最长路径(树的直径)

1072. 树的最长路径

题意:找到一条路径,使得路径上的边的权值之和最大

思路:d1[u] 和d2[u] 分别表示以u为根节点向下的最长/次长路径长度

          经过点u的最长路径长度,必然等于max(d1[u]+d2[u])

int d1[N],d2[N];
void dfs(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		//子节点j的最长路径+父节点u到子节点j的距离 
		dfs(j,u);
		int t=d1[j]+w[i];
		if(t>d1[u]){//更新最长路 
			d2[u]=d1[u];
			d1[u]=t;
		}else if(t>d2[u]){//更新次长路 
			d2[u]=t;
		}
	}
	res=max(res,d1[u]+d2[u]);
}

补充:另一种求树的直径的方式 (存在负权边时该方法不成立

①从任意点开始,找到离他最远的点u 
②再从点u开始,找离u最远的点 
③此时u离u最远的点距离即为树的直径

struct edge{
	int id;
	int w;
};
vector<edge> h[N];//h[i]以i为顶点的边 
int dis[N];
void dfs(int u,int father,int d){
	dis[u]=d;
	for(int i=0;i<h[u].size();i++){
		edge t=h[u][i];
		if(t.id==father) continue;
		dfs(t.id,u,d+t.w);
	}
}
int main(){
	cin>>n;
	for(int i=0;i<n-1;i++){
		int a,b,c;
		cin>>a>>b>>c;
		h[a].push_back({b,c});
		h[b].push_back({a,c});
	}
	//从任意点开始,找到离他最远的点u 
	dfs(1,-1,0);
	int u=1;
	for(int i=1;i<=n;i++)
		if(dis[i]>dis[u])
			u=i;
	//再从点u开始,找离u最远的点 
	dfs(u,-1,0);
	for(int i=1;i<=n;i++)
		if(dis[i]>dis[u])
			u=i;
	//此时u离u最远的点距离即为树的直径 
	int res=dis[u];
}

树的中心(换根DP)

1073. 树的中心

题意:找出离其他结点的最远距离最近的一点

思路:  d1[u] 和d2[u] 分别表示u向下的最长/次长路径长度

             p[u]  表示u的最长向下路径的子节点 

             up[u]  表示u向上的最长路径长度

        先dfs_down,求出d1[u]d2[u],同时记录p[u]

        再dfs_up,对于每个点j (父节点u) ,从up[u] 、d1[u](或d2[u],根据p[u]确定)选出较长的一条来计算 up[j]

         此时每个点都有d1[u] d2[u] up[u]即向下的最长、次长距离,向上的最长距离,根据题意计算即可

int d1[N],d2[N],up[N]; 
int p[N];
void dfs_down(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs_down(j,u);
		int t=d1[j]+w[i];
		if(t>d1[u]){
			d2[u]=d1[u];
			d1[u]=t;
			p[u]=j;//最长向下路径的子节点 
		}else if(t>d2[u]){
			d2[u]=t;
		}
	}
}
void dfs_up(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		up[j]=up[u]+w[i];
		if(p[u]==j){//j在u的最长向下路径上 
			up[j]=max(up[j],d2[u]+w[i]);
		}else{
			up[j]=max(up[j],d1[u]+w[i]);
		}
		dfs_up(j,u);
	}
}

    dfs_down(1,-1);
	dfs_up(1,-1);
	int res=0x3f3f3f3f;
	for(int i=1;i<=n;i++){
		int d=max(max(d1[i],d2[i]),up[i]);
		res=min(res,d);
	}
	cout<<res;

数字转换

1075. 数字转换

根据给定数和约数之和的关系构建树,然后求树的直径即可

约数之和->质数、约数

int d1[N],d2[N];
void dfs_down(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs_down(j,u);
		int t=d1[j]+w[i];
		if(t>d1[u]){
			d2[u]=d1[u];
			d1[u]=t;
		}else if(t>d2[u]){
			d2[u]=t;
		}
	}
	res=max(res,d1[u]+d2[u]);
}
int cal(int x){
	int tx=x;
	map<int,int> ms;
	for(int i=2;i<=x/i;i++){
		if(x%i==0){
			int s=0;
			while(x%i==0){
				x/=i;
				s++;
			}
			ms[i]+=s;
		}
	}
	if(x>1) ms[x]++;
	
	int sum=1;
	for(auto t:ms){
		int p=t.first,a=t.second;
		int ts=1;
		while(a--){
			ts*=p;
			ts++;
		}
		sum*=ts;
	}
	//题目要求约数之和中不包含本身 
	return sum-tx;
}
int main(){
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;i++){
		int t=cal(i);
		//题目要求正整数范围内,将0去除 
		if(t==0) continue;
		if(t<i){
			add(t,i,1);add(i,t,1);
		}
	}
	dfs_down(1,-1);
	cout<<res;
} 

二叉苹果树

1074. 二叉苹果树

题意:一颗二叉树,每个树枝有相应得分,只能保留q个树枝,求最大得分

思路:f[i][j] 表示以i为根节点的树,树枝不超过j个 的最大苹果数

           对于节点u的每个子节点j,枚举以u为根的子树所占的树枝个数k,再枚举以j为根的子树所占的树枝个数z,由于u和j之间还有一根树枝需要预留,所以z<=k-1

           转移方程:f[u][k]=max(f[u][k],f[u][k-z-1]+f[j][z]+w[i])

int f[N][N];
//f[i][j] 以i为根节点的树,树枝不超过j个 的最大苹果数 
void dfs(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
        //相当于01背包的一维,需要倒序更新 
		for(int k=q;k>=0;k--){
			for(int z=0;z<k;z++){
				f[u][k]=max(f[u][k],f[u][k-z-1]+f[j][z]+w[i]);
			}
		}
	}
}
//dfs(1,-1)
//f[1][q]

战略游戏

323. 战略游戏

题意:在一棵树的n个节点上放置士兵,每条边的两个端点至少有一个士兵,求最少放多少

 思路:f[i][0/1]  表示节点i放/不放的情况下,以i为根节点的子树最少放多少个

 对于节点u,①如果u上没有放置,则子节点j上必须放置,f[u][0]+= f[j][1]

 ②如果u上放置了,则子节点j上放不放均可,取较小值,f[u][1]+=min(f[j][0],f[j][1])

注意:如果u上放置,在f[u][1]中需要加上u本身的一个,即f[u][1]=1

int f[N][2];
//f[i][0/1] 节点i 放/不放 
void dfs(int u,int fa){
	//初始化u的状态 
	f[u][0]=0,f[u][1]=1; 
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
		//如果u没放,则j必须放
		//如果u放了,则j放不放都可以 
		f[u][0]+=f[j][1];
		f[u][1]+=min(f[j][0],f[j][1]);
	}	
}
//dfs(0,-1)
//min(f[0][0],f[0][1])

皇宫看守

1077. 皇宫看守

题意:在一棵树的n个节点上放置士兵,每个位置有各自的花费,每个节点要么放置,要么其邻点放置,求最小花费(区别于上一题,上一题中要求的是边,这一题要求的是点)

思路:

int f[N][3];
//f[i][0/1/2]
//节点i不放 其父节点放 
//节点i不放 其子节点放
//节点i放 
void dfs(int u){
	//初始化u的状态 
	f[u][0]=0;
	f[u][1]=0x3f3f3f3f;
	f[u][2]=w[u]; 
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		dfs(j);
		f[u][0]+=min(f[j][1],f[j][2]);
		f[u][2]+=min(f[j][0],min(f[j][1],f[j][2]));
	}	
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		f[u][1]=min(f[u][1],f[u][0]+f[j][2]-min(f[j][1],f[j][2]));
	}
}
//dfs(root);
//cout<<min(f[root][1],f[root][2])

叶子的颜色

1079. 叶子的颜色 - AcWing题库

(1)每个节点有三种选择:染白色、染黑色和不染色,但可以证明染白/黑色一定包含最优解

(2)状态转移:

(3)根节点的选择,可以证明选择任意一个根节点的结果都相同

#include<bits/stdc++.h>
using namespace std;
#define fast(); ios::sync_with_stdio(false);cin.tie(0),cout.tie(0); 
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e4+5,M=N<<1,INF=0x3f3f3f3f;
int n,m;
int c[N];
int h[N],e[M],ne[M],idx;
int f[N][2];//f[i][0/1] 以i为根节点的子树,且i染白/黑色,该子树的染色节点数量 
int d[N]; 
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
int dfs(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
		f[u][0]+=min(f[j][0]-1,f[j][1]);
		f[u][1]+=min(f[j][1]-1,f[j][0]);
	}
	return min(f[u][1],f[u][0]);
}
int main(){
	memset(h,-1,sizeof h);
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}	
	for(int i=1;i<m;i++){
		int a,b;cin>>a>>b;
		d[a]++,d[b]++;
		add(a,b);add(b,a);
	}
	for(int i=1;i<=m;i++){
		if(i<=n){//叶节点 
			f[i][c[i]]=1;
			f[i][!c[i]]=INF;
		}else{
			f[i][0]=f[i][1]=1;
		}
	} 
	cout<<dfs(n+1,-1)<<endl;
} 

生命之树

1220. 生命之树 - AcWing

题意:求权值之和最大的子树的和

        f[i][0/1] 表示以i为根的子树,选i或不选i,得到的最大权值之和

        ① 由于要保证选择的点联通,当不选i时,最多只能选一个子分支,即f[u][0]=max(f[j][0],f[j][1])

        ② 当选i时,选择所有分数为正的分支,即f[u][1]+=max(0,f[j][1])

        最后枚举所有子树,求的最大值即为答案

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e5+5,M=N<<1,INF=0x3f3f3f3f;
int n;
int h[N],e[M],ne[M],idx;
int s[N];
ll f[N][2];
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
void dfs(int u,int fa){
	f[u][1]=s[u];
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
		f[u][0]=max(f[j][0],f[j][1]);
		f[u][1]+=max(0ll,f[j][1]);
	}
}
int main(){
	memset(h,-1,sizeof h);
	memset(f,-0x3f,sizeof f);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>s[i];
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u);
	}
	dfs(1,-1);
	ll res=-0x3f3f3f3f3f3f3f3f;
	for(int i=1;i<=n;i++){
		res=max(res,max(f[i][0],f[i][1]));
	}
	cout<<res<<endl;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
树形动态规划(Tree DP)是一种常用的动态规划算法,用于解决树结构相关的问题。在Python中,可以使用递归或者迭代的方式实现树形DP树形DP的基本思想是,从树的叶子节点开始,逐层向上计算每个节点的状态,并利用已经计算过的节点状态来更新当前节点的状态。这样可以通过自底向上的方式,逐步计算出整个树的最优解。 下面是一个简单的示例,演示如何使用树形DP解决一个二叉树中节点权值之和的最大值问题: ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def max_sum(root): if root is None: return 0 # 递归计算左右子树的最大权值和 left_sum = max_sum(root.left) right_sum = max_sum(root.right) # 当前节点的最大权值和为当前节点值加上左右子树中较大的权值和 return root.val + max(left_sum, right_sum) # 构建一个二叉树 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) # 计算二叉树中节点权值之和的最大值 result = max_sum(root) print(result) ``` 这段代码中,我们定义了一个`TreeNode`类来表示二叉树的节点,其中`val`表示节点的权值,`left`和`right`分别表示左子节点和右子节点。`max_sum`函数使用递归的方式计算二叉树中节点权值之和的最大值,通过比较左右子树的最大权值和来确定当前节点的最大权值和。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vic.GoodLuck

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值