树形dp(+换根dp+dp结合)

求树的重心

(重心:指树中的一个结点,如果将此结点删除后剩余各个连通块中点数的最大值最小。那么此结点被称为树的重心)
LINK
题目:
在这里插入图片描述

在这里插入图片描述

思路:

遍历每一结点,在此结点中判断是此结点的最大子树比较大还是非此结点子树的节点数比较大
下面用图来稍作解释:
在这里插入图片描述

代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
vector<int>g[N];
int f[N];//以u为根的子树的结点总数(不包括根) 
int n,a,b,mans=0x3f3f3f3f,manode;
void add(int x,int y){
	g[x].push_back(y);
}
void dfs(int son,int fa){
	f[son]=1;int maxx=0;
	for(int i=0;i<g[son].size();i++){
		int j=g[son][i];
		if(j==fa)continue;//防止往回走 
		dfs(j,son);
		f[son]+=f[j];//加上各子树的点
		maxx=max(maxx,f[j]);//寻找以fa为根的最大子树 
	}
	maxx=max(maxx,n-f[son]);//比较是fa的最大子树比较大还是非fa子树的节点数比较大 
	if(maxx<mans){//寻找最大值尽可能小 
		mans=maxx;manode=son;
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++)cout<<f[i]<<" ";cout<<endl;
	cout<<manode<<" "<<mans<<endl;
	return 0; 
}

换根dp:

通常来说需要两次dfs,第一次从叶子到根;第二次从根到叶子。

求树的深度之和最大

(换根dp)

LINK
在这里插入图片描述

思路:
以1为根结点时,用dfs寻找树的深度之和,同时记录下各结点此时孩子的数量,然后dfs每点为根时运用公式(换根dp)求解;
下面用图中例子简单模拟:
在这里插入图片描述

代码:

1:数组
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
struct node{
	int to,next;
}e[N<<1];
int n,cnt,id;
int head[N];
ll f[N],dep[N],size[N],ans;
//size[i]:i的孩子的个数
//dep[i]:i的深度
//f[i]:以i为根的树的深度和 
inline void add(int u,int v){//邻接表 
	e[++cnt].next=head[u];//连接的边 
	head[u]=cnt; //连接的下一条边 
	e[cnt].to=v; //这条边到达的点 
}
void dfs1(int x,int fa){
	size[x]=1;//每个位置初始为一个结点 
	dep[x]=dep[fa]+1;//子结点的深度=父节点深度+1 
	for(int i=head[x];i;i=e[i].next){//遍历x的子结点 
		int j=e[i].to ;//连接的点 
		if(j==fa)continue;
		dfs1(j,x);
		size[x]+=size[j];//加上孩子为根时所拥有的节点数 
	}
}
void dfs2(int x,int fa){
	for(int i=head[x];i;i=e[i].next){//遍历x的子结点 
		int y=e[i].to;
		if(y==fa)continue;
		//y为x的子结点 
		f[y]=f[x]+n-2*size[y];//x:当前结点 y:每个儿子结点 
		dfs2(y,x);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int v,u;cin>>u>>v;
		add(u,v);	add(v,u);
	}
	dfs1(1,0);//以1为根寻找每个子树的结点数 
	for(int i=1;i<=n;i++)f[1]+=dep[i];//计算以1为根时的深度和 
	dfs2(1,0);
	for(int i=1;i<=n;i++)
		if(ans<f[i]){
			ans=f[i];id=i;
		}
	cout<<id<<endl;
	return 0;
}
2:vector:
推荐这样使用
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
vector<int>e[N]; 
int n,cnt,id;
ll f[N],dep[N],size[N],ans;
//size[i]:i的孩子的个数
//dep[i]:i的深度
//f[i]:以i为根的树的深度和 
void dfs1(int x,int fa){
	size[x]=1;//每个位置初始为一个结点 
	dep[x]=dep[fa]+1;//子结点的深度=父节点深度+1 
	for(int i=0;i<e[x].size();i++){//遍历x的子结点 
		int y=e[x][i];
		if(y==fa)continue;
		dfs1(y,x);
		size[x]+=size[y];
	}
}
void dfs2(int x,int fa){
	for(int i=0;i<e[x].size();i++){//遍历x的子结点 
		int y=e[x][i];
		if(y==fa)continue;
		f[y]=f[x]+n-2*size[y];
		dfs2(y,x);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int v,u;cin>>u>>v;
		e[u].push_back(v);e[v].push_back(u);
	}
	dfs1(1,0);//以1为根寻找每个子树的结点数 
	for(int i=1;i<=n;i++)f[1]+=dep[i];//计算以1为根时的深度和 
	dfs2(1,0);
	for(int i=1;i<=n;i++)
		if(ans<f[i]){
			ans=f[i];id=i;
		}
	cout<<id<<endl;
	return 0;
}
另一种
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
vector<int>e[N]; 
int n,cnt,id;
ll f[N],dep[N],size[N],ans;
//size[i]:i的孩子的个数
//dep[i]:i的深度
//f[i]:以i为根的树的深度和 
void dfs1(int x,int fa){
	size[x]=1;//每个位置初始为一个结点 
	dep[x]=dep[fa]+1;//子结点的深度=父节点深度+1 
	for(auto i:e[x]){//遍历x的子结点 
		if(i==fa)continue;
		dfs1(i,x);
		size[x]+=size[i];
	}
}
void dfs2(int x,int fa){
	for(auto i:e[x]){//遍历x的子结点 
		if(i==fa)continue;
		f[i]=f[x]+n-2*size[i];
		dfs2(i,x);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int v,u;cin>>u>>v;
		e[u].push_back(v);e[v].push_back(u);
	}
	dfs1(1,0);//以1为根寻找每个子树的结点数 
	for(int i=1;i<=n;i++)f[1]+=dep[i];//计算以1为根时的深度和 
	dfs2(1,0);
	for(int i=1;i<=n;i++)
		if(ans<f[i]){
			ans=f[i];id=i;
		}
	cout<<id<<endl;
	return 0;
}

求树的边权值最小

LINK

在这里插入图片描述
输出格式
一行一个整数,表示最小的不方便值。

输入输出样例
输入
5
1
1
0
0
2
1 3 1
2 3 2
3 4 3
4 5 3
输出
15

思路:

以1为树的根统计此时每个节点下的牛奶数量以及从叶子到此位置时的价值;
以下例子简单理解一下:
在这里插入图片描述

代码:
数组:(更快)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
struct node{
	int to,next,w;
}e[N<<1];
ll n,sum,ans,cnt;
int head[N];
ll f[N],size[N],a[N];
inline void add(int u,int v,int w){
	e[++cnt].next=head[u];
	head[u]=cnt;
	e[cnt].to=v;e[cnt].w=w;
}
void dfs1(int x,int fa){
	size[x]=a[x];
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].to ,w=e[i].w ;
		if(v==fa)continue;
		dfs1(v,x);
		size[x]+=size[v];//统计奶牛数量 
		f[x]+=f[v]+w*size[v];//从x的子树到x所用的价值 
	}
}
void dfs2(int x,int fa){
	for(int i=head[x];i;i=e[i].next ){
		int y=e[i].to ,w=e[i].w ;
		if(y==fa)continue;
		f[y]=f[x]-w*size[y]+(sum-size[y])*w;
		ans=min(ans,f[y]);
		dfs2(y,x);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
	for(int i=1;i<n;i++){
		int u,v,w;cin>>u>>v>>w;
		add(u,v,w);add(v,u,w);
	}
	dfs1(1,0);
	ans=f[1];
	dfs2(1,0);
	cout<<ans<<endl;
	return 0;
}
vector:(两个只是循环上的差别,速度差不多 int相对比auto快一点点)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
vector<pair<int,int>>e[N]; 
ll n,sum,ans;
ll f[N],size[N],a[N];
void dfs1(int x,int fa){
	size[x]=a[x];
	for(int i=0;i<e[x].size();i++){
		int v=e[x][i].first,w=e[x][i].second;
		if(v==fa)continue;
		dfs1(v,x);
		size[x]+=size[v];//统计奶牛数量 
		f[x]+=f[v]+w*size[v];//从x的子树到x所用的价值 
	}
}
void dfs2(int x,int fa){
	for(int i=0;i<e[x].size();i++){
		int y=e[x][i].first,w=e[x][i].second;
		if(y==fa)continue;
		f[y]=f[x]-w*size[y]+(sum-size[y])*w;
		ans=min(ans,f[y]);
		dfs2(y,x);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];//sum:统计奶牛总数
	for(int i=1;i<n;i++){
		int u,v,w;cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dfs1(1,0);
	ans=f[1];
	dfs2(1,0);
	cout<<ans<<endl;
	return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
vector<pair<int,int>>e[N]; 
ll n,sum,ans;
ll f[N],size[N],a[N];
void dfs1(int x,int fa){
	size[x]=a[x];
	for(auto i:e[x]){
		int v=i.first,w=i.second;
		if(v==fa)continue;
		dfs1(v,x);
		size[x]+=size[v];//统计奶牛数量 
		f[x]+=f[v]+w*size[v];//从x的子树到x所用的价值 
	}
}
void dfs2(int x,int fa){
	for(auto i:e[x]){
		int y=i.first,w=i.second;
		if(y==fa)continue;
		f[y]=f[x]-w*size[y]+(sum-size[y])*w;
		ans=min(ans,f[y]);
		dfs2(y,x);
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
	for(int i=1;i<n;i++){
		int u,v,w;cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dfs1(1,0);
	ans=f[1];
	dfs2(1,0);
	cout<<ans<<endl;
	return 0;
}

指定树枝数量求保留树枝边权和最大

树形dp+01背包

(太菜了 这居然是简单题???)
LINK
在这里插入图片描述
输入输出样例
输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出
21
说明/提示
1 ⩽ Q < N ⩽ 100 , 每 根 树 枝 上 的 苹 果 ⩽ 3 × 1 0 4 1 \leqslant Q < N \leqslant 100,每根树枝上的苹果 \leqslant 3 \times10^4 1Q<N1003×104

思路:

题目要求以1为根,用dfs从叶子节点向上更新,利用01背包存放各点保留枝条数目的权值和;
(前提:以1为根)
f[i][j]:i的子树上保留j条边时最大权值和
注意在dp时f的[j-k-1]操作中-1是因为x与y之间也有一条边,所以后面需要加上它们之间的边权值

代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e2+10;
typedef long long ll;
ll n,m;
ll f[N][N],size[N],a[N];
//size[i]:i子树的边数 
vector<pair<int,int>>e[N];
void dfs(int x,int fa){
	for(int i=0;i<e[x].size();i++){//遍历儿子 
		int y=e[x][i].first,w=e[x][i].second;
		if(y==fa)continue;
		dfs(y,x);//从叶子节点开始向上 
		size[x]+=size[y]+1;//统计边数 
		for(int j=min(size[x],m);j;j--){//x边的数量 
			for(int k=0;k<=min(size[y],j-1ll);k++)//y边的数量 
//			for(int k=min(size[y],j-1ll);k>=0;k--)	//也可以		
				f[x][j]=max(f[x][j],f[x][j-k-1]+f[y][k]+w);//w:x与y之间的边权 
		}
	}
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<n;i++){
		int u,v,w;cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dfs(1,0);
	cout<<f[1][m];
	return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值