题面描述:
给定一棵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多经过的次数就是 :
就是除了当前点的子树和他的父亲以外的点的个数,因为到这些点必须经过这条边,所以就多了这些次数。
就是它的子树的个数,它的父亲到她的子树时是经过了这条边的,但是它下去却不需要经过,所以需要减掉。
经过整理后变成:
假设dp[i]表示以i为根到其他点的距离和。
我们推得以下公式:
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;
}