【题解】树形dp例题5

题面描述:

给定一棵n个节点的树,求其中每个点到其他节点的距离和。


输入格式

第一行一个整数n; 接下来n-1行是三个数x,y,z,表示x到y有一条长度为z的边; 数据保证给出的是一棵树。


输出格式

n行,一行一个数,第i行数字表示点i到其他点的距离和。


样例数据

input
3
1 2 3
1 3 5

output
8
11
13


分析:

我们先来考虑一下只做一个点的距离和该怎么做

很显然,一遍dfs遍历整张图累加距离和即可。

void dp(int x,LL last){
    vis[x]=1;//当前点是否遍历过
    num[x]=1;//以他为根的点的个数
    sum+=last;//last是根节点到当前点的距离,因为距离和并不是每条边只经过一次,边是要重复累加的,所以我们在累加last的时候直接类加上权值
    for (int i=linkk[x];i;i=e[i].Next){
	    int y=e[i].y;
	    LL v=e[i].v;
	    if (vis[y]) continue;
	    dp(y,last+e[i].v);//继续遍历
	    num[x]+=num[y];//累加上点的个数
	}
}

考虑完一个点的做法后我们再来想多个点的做法

其实对于到某个点,它到其他点的距离和只和到它父亲的权值有关。
对于其他边,它走的次数是和它父亲走的次数是一样的。
但是它和父亲之间的连边经过的次数就会发生改变。
例如下图(边权假设为1)
在这里插入图片描述我们先算出每个点的子树大小。
有下图:
在这里插入图片描述
假设我们现在要求以2为根到其他点的距离。
2的父亲是1
其他边经过的次数不变,但是1->2的边的经过次数发生了变化
父亲1到点4,5时,经过1->2的边的次数为2.
但是2到4,5却不用经过1->2的边。
所以以二为根的距离和比以1位根的距离和少了两条1->2的边
2到3,4,7经过1->2的边的次数为3
但是1到3,4,7却不用经过这条边。
所以以二为根的距离和比以1位根的距离和多了三条1->2的边
总体多了1条1->2的边。

我们发现:
对于一个点y,他的父亲是x,边权是e[i].v
e[i].v多经过的次数就是 :
( n n u m [ y ] 1 ) ( n u m [ y ] 1 ) (n-num[y]-1)-(num[y]-1)
n n u m [ y ] 1 n-num[y]-1 就是除了当前点的子树和他的父亲以外的点的个数,因为到这些点必须经过这条边,所以就多了这些次数。

n u m [ y ] 1 num[y]-1 就是它的子树的个数,它的父亲到她的子树时是经过了这条边的,但是它下去却不需要经过,所以需要减掉。

经过整理后变成:
n 2 n u m [ y ] n-2*num[y]

假设dp[i]表示以i为根到其他点的距离和。
我们推得以下公式:
d p [ y ] = d p [ x ] + ( n 2 n u m [ y ] ) e [ i ] . v   ( y s o n ( x ) ) dp[y]=dp[x]+(n-2*num[y])*e[i].v\ (y\in son(x))

扫描二维码关注公众号,回复: 6765034 查看本文章

Code


#include<bits/stdc++.h>
using namespace std;
#define LL long long
struct node{
    int y,Next;
	LL v;
}e[1000010];
int linkk[1000010];
int fa[1000010];
int num[1000010];
LL fav[1000010];
LL Dp[1000010];
bool vis[1000010];
LL sum=0;
int n;
int len=0;
void insert(int x,int y,LL v){
    e[++len].Next=linkk[x];
    linkk[x]=len;
    e[len].y=y;
    e[len].v=v;
}
void dp(int x,LL last){
    vis[x]=1;
    num[x]=1;
    sum+=last;
    for (int i=linkk[x];i;i=e[i].Next){
	    int y=e[i].y;
	    LL v=e[i].v;
	    if (vis[y]) continue;
	    fa[y]=x;
	    fav[y]=e[i].v;
	    dp(y,last+e[i].v);
	    num[x]+=num[y];
	}
}//第一个预处理
void treedp(int x){
    vis[x]=1;
    for (int i=linkk[x];i;i=e[i].Next){
	    int y=e[i].y;
	    if (vis[y]) continue;
	    Dp[y]=Dp[x]+(n-num[y])*e[i].v-(num[y])*e[i].v;//之前那个公式
	    treedp(y);
	}
}
int main(){
	freopen("t5.in","r",stdin);
	freopen("t5.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<n;i++){
	    int x,y;
	    LL v;
	    scanf("%d %d %lld",&x,&y,&v);
	    insert(x,y,v);
	    insert(y,x,v);
	}
	fa[1]=0;
	dp(1,0);
	printf("%lld",sum);
	Dp[1]=sum;//假设1是根,那么1就是sum值
	memset(vis,0,sizeof(vis));
	treedp(1);
	for (int i=2;i<=n;i++) printf("\n%lld",Dp[i]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/huang_ke_hai/article/details/88534703
今日推荐