常见树形dp递归更新方法

常见的树形dp递归更新方法有两种,一种是在递归完回溯的时候用子节点更新父节点,而另一种就是在递归过程中用父节点更新子节点。

先来展现一下两种递归方式的模板差异:

void dfs1(int x,int father)//用子节点更新父节点,在回溯过程中更新 
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dfs1(j,x);
		//此处放用子节点更新父节点代码 
	}
}

void dfs2(int x,int father)//用父节点更新子节点,在递归过程中更新 
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		//此处放用父节点更新子节点代码 
		dfs1(j,x);
	}
}

光这样说有点抽象了,下面举两个例子让大家好好理解一下。

A school bought the first computer some time ago(so this computer's id is 1). During the recent years the school bought N-1 new computers. Each new computer was connected to one of settled earlier. Managers of school are anxious about slow functioning of the net and want to know the maximum distance Si for which i-th computer needs to send signal (i.e. length of cable to the most distant computer). You need to provide this information.



Hint: the example input is corresponding to this graph. And from the graph, you can see that the computer 4 is farthest one from 1, so S1 = 3. Computer 4 and 5 are the farthest ones from 2, so S2 = 2. Computer 5 is the farthest one from 3, so S3 = 3. we also get S4 = 4, S5 = 4.

Input

Input file contains multiple test cases.In each case there is natural number N (N<=10000) in the first line, followed by (N-1) lines with descriptions of computers. i-th line contains two natural numbers - number of computer, to which i-th computer is connected and length of cable used for connection. Total length of cable does not exceed 10^9. Numbers in lines of input are separated by a space.

Output

For each case output N lines. i-th line must contain number Si for i-th computer (1<=i<=N).

Sample Input

5
1 1
2 1
3 1
1 1

Sample Output

3
2
3
4
4

 先说一下题意:就是给你描绘一张图,给你点与点之间的边及其权值,让你求出来每个点距离图中其他点之间的最大距离。

我们需要维护一个数组dp[][];

dp[i][1]表示以i为根节点的子树中的点到i的最大距离
dp[i][2]表示以i为根节点的子树中的点到i的次大距离
dp[i][3]表示除以i为根节点的子树中的点之外的点到i的最大距离

 就那这张图来说,dp[x][1]和dp[x][2]分别表示以x为根节点的子树中的点到达x点的最大距离和次大距离,也就是x沿着l1和l2方向搜索所得到的最大距离和次大距离   dp[x][3]表示x沿着父亲节点的方向搜索得到的最大距离,也就是沿着l3方向所得到的最大距离,如果我们能够得到这个dp数组,那很容易就知道在图中距离某个节点的最大距离的点要么是从x往子节点方向搜索得到的,要么就是从x往父亲节点方向搜索得到的,也就是对dp[x][1]和dp[x][3]取一个最大值就好

那我们如何更新这个dp数组呢?依旧是以这个图片为例

先来说一下dp[x][1]和dp[x][2]怎么更新,这个比较简单,只要我们从根节点向子节点搜素,然后在回溯的过程中取x所有的子节点加上其到x点的边权的最大值就好。

那dp[j][3]怎么更新呢?我们只能用父节点去更新子节点,所以我以j号点为例,j号点是x号点的一个子节点,不难想到的是由j号点向父亲节点x方向搜索所能得到的最大值可能有两个途径,一是从j号点到x号点再往x号点的父亲节点方向搜索得到的,另一个就是从j号点到x号节点再向不经过j号点的其他的x子节点方向搜索得到,而第一个我们已经找到,也就是dp[x][3],至于第二个么我们就要分情况讨论一下了,这也是为什么我们要维护以x号点为根的子树中其他的点到x号点的次大距离的原因,如果从x号点向j号点的路径就是以x号点为根的子树中的点到x的最大距离所对应的路径时,我们就要取到x的次大距离+x与j的边权作为从j号点到x号节点再向不经过j号点的其他的x子节点方向搜索得到的最大值,反之我们就要取到x的最大距离+x与j的边权作为从j号点到x号节点再向不经过j号点的其他的x子节点方向搜索得到的最大值,最后对我上面所说的两种情况取一个最大值就得到了dp[j][3],细节见代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=3e4+10;
typedef long long ll;
int h[N],ne[N],e[N],w[N],idx;
ll dp[N][3];
//dp[i][1]表示以i为根节点的子树中的点到i的最大距离
//dp[i][2]表示以i为根节点的子树中的点到i的次大距离
//dp[i][3]表示除以i为根节点的子树中的点之外的点到i的最大距离 
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs1(int x,int father)//用子节点更新父节点,先深搜再更新 
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dfs1(j,x);
		if(dp[j][1]+w[i]>dp[x][1])
		{
			dp[x][2]=dp[x][1];
			dp[x][1]=dp[j][1]+w[i];
		}
		else if(dp[j][1]+w[i]>dp[x][2])
			dp[x][2]=dp[j][1]+w[i];
	}
}
void dfs2(int x,int father)//用父节点更新子节点,先更新再深搜 
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		if(dp[j][1]+w[i]==dp[x][1])//j号节点在x到以x为根的子树中距离x最远的点的路径上 
			dp[j][3]=max(dp[x][3]+w[i],dp[x][2]+w[i]);//取父节点的次长值+1和经过父节点向上搜到的最远距离+1之中的最大值 
		else//j号节点不在x到以x为根的子树中距离x最远的点的路径上 
			dp[j][3]=max(dp[x][3]+w[i],dp[x][1]+w[i]);
		dfs2(j,x);
	}
}
int main()
{
	int n;
	int u,v;
	while(cin>>n)
	{
		memset(h,-1,sizeof h);
		memset(dp,0,sizeof dp);
		idx=0;
		for(int i=2;i<=n;i++)
		{
			scanf("%d%d",&u,&v);
			add(i,u,v);add(u,i,v);
		}
		dfs1(1,0);
		dfs2(1,0);
		for(int i=1;i<=n;i++)
		printf("%lld\n",max(dp[i][1],dp[i][3]));
	}
	return 0;
}

再来跟大家分享一道题目

The massive tsunami that struck the coastal city has  washed away many of 

inhabitants and facilities there. After the tsunami,  the power supply facilities  of  the 

coastal city  are completely destroyed. People are in panic in the dark night. Doubts 

remain over whether the communities will be able to rebuild the city. To calm people 

down, the heads of the city are planning to rebuild  the city to start with the recovery 

of the power supply facilities. 

The coastal city consists of n communities which are numbered from 1 to n. To 

save the electric cables,  n-1  cables has been used to connect these  communities 

together, so that each pair of communities  is able to transfer electronic energy 

mutually.   

The heads of the city decide to set a power station in one of the communities. 

There  is  thermal energy loss along the cables. Each cable has a resistance of R ohm. 

The total  thermal energy loss is the sum of  I^2*Ri . Here Ri is the total residence along 

the path between the ith community and the power station, and I is a constant. They 

are troubling their head on the issue of where to set the power station to make the total 

thermal energy loss minimized.

Input

There are multiple test cases. 

The first line contains one integer indicating the number of test cases. 

For each test case, the first line contains three positive integers  n,  I  and  R, 

indicating  the number of communities, the  above mentioned constant  and the 

residence of each cable. (3 ≤ n ≤ 50000, 1 ≤ I ≤ 10, 1 ≤ R ≤ 50) 

The next n-1 lines, each describe a cable connection by two integers X, Y, which 

indicates that between community X and community Y, there is a cable. 

Output

For each test case, please output two lines. 

The first line is the minimum total thermal energy loss. 

The second line is all the optional communities in ascending order. 

You are requested to leave a blank line after each test case. 

输入输出示例链接 :http://poj.org/ProblemDescriptions/jinghua.pdf

先来说一下大概题意:就是给你一棵树,让你在树中选一个点建立发电站,要求其他点到这个点的距离和最小,注意可能有多种情况。

这道题的数据量是不允许我们在每个点都跑一次最短路的,我们可以先开一个数组记录以某个点为根节点的子树中的结点个数,为什么要维护一个这样的数组呢?我们要更新除了以这个点为根的子树中的点之外的点到这个点的距离和,现在不明白没关系,一会我会给出图片解释,我们还需要求以这个点为根的子树中的点到x的距离和,维护这个数组应该挺简单的,最后我们只需要把以x为根的子树中的点到x的距离和与除了以x点为根的子树中的点之外的点到这个点的距离和相加就得到了所有点到x的距离和。现在剩下的最主要的问题就是怎么维护除了以某个点为根的子树中的点之外的点到这个点的距离和,先来用图片说明一下

依旧是用这个图片来说明,以x和其子节点j为例,设维护以x为根的子树中的节点总数的数组为cnt数组,维护以x为根的子树中的点到x的距离和的数组为dp[x][1],这两个都比较好处理,在这我就不多说了,细节看代码就好,现在来说一下如何维护除了以某个点为根的子树中的点之外的点到这个点的距离和,我拿j点来说,x点往其父亲节点的方向,也就是向l3方向搜索得到的距离总和已经在之前被计算过了,现在我们来计算x往除了j这个子节点以外的其他子节点搜索得到的距离总和,这不就是用以x为根的子树中的点到x的距离和减去以j为根的子树中的点到j的距离和再减去x与j的连边的权值么,也就是dp[x][1]-dp[j][1]-d(x,j),到这,我们已经计算出来除了以j为根的子树中的点之外的点到x的距离和,最后只要加上除了以j为根的子树中的点之外的点的数目*d(j,x)不就得到了j点往其父亲节点方向搜索得到的点的距离和了么,所有的数组我们都处理完毕了,下面就看代码吧!

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int h[N],ne[N],e[N],idx;
int cnt[N];//cnt[i]记录以i为根的子树中节点的个数
ll dp[N][3];
//dp[i][1]表示以i为根的子树中所有点到i的距离和
//dp[i][2]表示除了以i为根的子树中的点的所有点到i的和,也就是向自己的父节点遍历 
int n,I,R;
void add(int x,int y)
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs1(int x,int father)//递归维护以x为根的子树中的节点个数 
{
	cnt[x]=1;
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dfs1(j,x);
		cnt[x]+=cnt[j];//父亲节点加上每一个子节点的节点个数 
	}
}
void dfs2(int x,int father)//用子节点更新父节点 
{
	dp[x][1]=0;
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dfs2(j,x);
		dp[x][1]+=dp[j][1]+cnt[j];//以子节点为根的子树中的所有点的距离都要加1,所以总共要加cnt[j]
	} 
}
void dfs3(int x,int father)//用父节点更新子节点 
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dp[j][2]=(n-cnt[j])+dp[x][2]+dp[x][1]-dp[j][1]-cnt[j];
		//(n-cnt[j])代表除了以j为根的子树中的点之外的点 每个点加1是因为x与j之间有一条距离为1的边
		//dp[x][2]是x往父亲方向搜索到的距离和
		//dp[x][1]-(dp[j][1]+cnt[j])计算的是x到除了j之外的以其他子节点为根的子树中的点的距离和 
		dfs3(j,x);
	} 
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		memset(h,-1,sizeof h);
		idx=0;
		cin>>n>>I>>R;
		int u,v;
		for(int i=1;i<n;i++)
		{
			scanf("%d%d",&u,&v);
			add(u,v);add(v,u);
		}
		dfs1(1,0);
		dfs2(1,0);
		dfs3(1,0);
		ll ans=0x3f3f3f3f;
		for(int i=1;i<=n;i++)
			ans=min(ans,dp[i][1]+dp[i][2]);
		printf("%lld\n",I*I*R*ans);
		for(int i=1;i<=n;i++)
			if(dp[i][1]+dp[i][2]==ans)
				printf("%d ",i);
		cout<<endl<<endl;
	}
	return 0;
}

树形dp更新一般就这两种方法,一种在回溯的过程中更新,另一种就是在搜索的过程中更新,我们在树形dp中所要维护的数组一般都通过这两种方式来维护,但是具体的维护方法就要根据具体的题目来说了,一般要求1个点到图中其他点所满足的最优性质,这种最优性质要么可以在向其子节点的方向上搜索得到要么就是向其父亲节点的方向搜索得到,这种问题我们就需要两次dfs分别维护不同的数组来实现。我今天就挑了两道比较经典的题目来分析的,如果大家还有什么更好的可以练习树形dp搜索的题目欢迎在评论区里面分享!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值