1405 树的距离之和
题意:给定一棵无根树,假设它有n个节点,节点编号从1到n, 求任意两点之间的距离(最短路径)之和。
思路:开个数组size记录每个节点和它子节点集合的大小,开个sum用来记录dfs过程中从根节点到子节点的距离,也就是距离前缀和,f用来记录每个节点到其他所有节点的距离。
2次dfs原因:
第一次:dfs我们可以从根节点向下求根节点到每个子节点的前缀和,用sum记录然后我们把每个节点的sum给了f[1]就求出根节点到所有节点的距离和了。同时回溯时,求出每个节点子集的大小,用size记录。
第二次dfs:已知根节点1到其他节点的距离和,然后还已知每个节点子集的大小,该怎么求其余每个节点到其他节点的距离和?可以利用根的转移。
对于遍历到的任意一个节点 i,对于与之相邻的节点 j 来说,答案贡献由 i 到 j 转移首先减小了 size[j]∗1size[j]∗1,
同时增加了 (n−size[j])∗1(n−size[j])∗1,因此可以直接得到f[j]=f[i]+n−size[j]∗2f[j]=f[i]+n−size[j]∗2。
如图,如果根转移到节点2,距离首先要减少size[2]*1,然后要增加(n-size[2])*1,因为根变为节点2,节点一到节点二子集的所有距离要消失,也就是红色部分(size[v]),要增加从节点2到节点一子集的距离也就是绿色部分(n-size[v]),得状态转移方程如下:
f[v]=f[u]+n-size[v]*2
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+10;
int head[2*maxn],ver[2*maxn],nxt[2*maxn];
int tot=0;
int n;
ll sum[maxn],size[maxn],f[maxn];
void add(int u,int v)
{
ver[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void dfs1(int u,int fa)
{
size[u]=1;
for(int i=head[u]; i; i=nxt[i])
{
int v=ver[i];
if(v==fa) continue;
sum[v]=sum[u]+1;
f[1]+=sum[v];
dfs1(v,u);
size[u]+=size[v];
}
}
void dfs2(int u,int fa)
{
for(int i=head[u]; i; i=nxt[i])
{
int v=ver[i];
if(v==fa) continue;
f[v]=f[u]+n-2*size[v];
dfs2(v,u);
}
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n-1; i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs1(1,0);
dfs2(1,0);
for(int i=1; i<=n; i++)
printf("%lld
",f[i]);
}