1405 树的距离之和 二次扫描换根法

1405 树的距离之和

题意:给定一棵无根树,假设它有n个节点,节点编号从1到n, 求任意两点之间的距离(最短路径)之和。

思路:开个数组size记录每个节点和它子节点集合的大小,开个sum用来记录dfs过程中从根节点到子节点的距离,也就是距离前缀和,f用来记录每个节点到其他所有节点的距离。

 

2次dfs原因:

第一次:dfs我们可以从根节点向下求根节点到每个子节点的前缀和,用sum记录然后我们把每个节点的sum给了f[1]就求出根节点到所有节点的距离和了。同时回溯时,求出每个节点子集的大小,用size记录。

第二次dfs:已知根节点1到其他节点的距离和,然后还已知每个节点子集的大小,该怎么求其余每个节点到其他节点的距离和?可以利用根的转移。

 

 如图,如果根转移到节点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\n",f[i]);
}

猜你喜欢

转载自www.cnblogs.com/dongdong25800/p/11127307.html