常见树形dp递归更新方法

194 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

常见的树形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.

image.png

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的最大距离

image.png

就那这张图来说,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;
}