51 nod 1405 树的距离之和 详细题解(树形DP)

本文解析了一道关于计算树中任意两点间距离之和的问题,通过递归算法求解节点间的距离,并介绍了一个有效的方法来计算每个节点作为根时,树中所有节点到该节点的距离之和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


基准时间限制:1 秒 空间限制:131072 KB 分值: 40  难度:4级算法题
 收藏
 关注
给定一棵无根树,假设它有n个节点,节点编号从1到n, 求任意两点之间的距离(最短路径)之和。
Input
第一行包含一个正整数n (n <= 100000),表示节点个数。
后面(n - 1)行,每行两个整数表示树的边。
Output
每行一个整数,第i(i = 1,2,...n)行表示所有节点到第i个点的距离之和。
Input示例
4
1 2
3 2
4 2
Output示例
5
3
5
5

思路:

首先,任选一个节点,设定为树的根。

用num[x]表示以节点x为根的子树的节点总数(包含x自身)

假如设定节点1为根,则先求出dp[1],表示所有节点到节点1的距离之和,

对根而言也是所有节点的深度之和。


若x是y的子结点,则有

dp[x] = dp[y] + (n-num[x]) - num[x];

因为x为根的子树的所有节点到x的距离比到y的距离少1,所以减num[x]

其余节点到x的距离比到y的距离多1,所以加 n-num[x]


做题感悟: 这题 一开始 我一直把重点放在随便一个节点身上,想着怎样才能推出一个万能的状态转移。。。但我忽略了,对于根,他所有的子树跟他的距离和就是他跟所有点的距离和。。。这可以算一个已知条件。。。而我一直找下面节点的状态。。。这时候又因为他的父节点推不出来一直想。。。 其实跟节点算个已知了。。应该根据这个往外推,我真zz。。。然后转移就是上面红字说的了。。。画个图想象一下就好。。。另外这题的跟节点是求每个节点的深度和。。。我这题是从下往上推得。。dp[fa] = dp[to] + cnt[to],因为父节点跟儿子节点之间这条连线要走cnt[to]遍。。。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1e5 + 5;
long long dp[maxn], n, deep[maxn], cnt[maxn];
vector<int> p[maxn];
void dfs(int x, int f)
{
    cnt[x] = 1;
    if(!p[x].size())
    {
        deep[x] = 0;
        return ;
    }
    for(int i = 0; i < p[x].size(); i++)
    {
        int to = p[x][i];
        if(to == f) continue;
        dfs(to, x);
        dp[x] += deep[to] + cnt[to];
        deep[x] += deep[to] + cnt[to];
        cnt[x] += cnt[to];
    }
}
void dfs2(int x, int f)
{
    for(int i = 0; i < p[x].size(); i++)
    {
        int to = p[x][i];
        if(to == f) continue;
        dp[to] = dp[x] - cnt[to] + n - cnt[to];
        dfs2(to, x);
    }
}
int main()
{
    int u, v;
    while(~scanf("%lld", &n))
    {
        for(int i = 1; i <= n; i++) p[i].clear();
        memset(dp, 0, sizeof(dp));
        memset(deep, 0, sizeof(deep));
        memset(cnt, 0, sizeof(cnt));
        for(int i = 1; i < n; i++)
        {
            scanf("%d%d", &u, &v);
            p[u].push_back(v);
            p[v].push_back(u);
        }
        dfs(1, -1);
        dfs2(1,-1);
        for(int i = 1; i <= n; i++)
        {
            printf("%lld\n", dp[i]);
        }
    }
    return 0;
}

其实直接求跟根节点的深度就好了啊。。。唉,zz如我

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1e5 + 5;
long long dp[maxn], n, deep[maxn], cnt[maxn];
vector<int> p[maxn];
void dfs(int x, int f)
{
    cnt[x] = 1;
//    if(!p[x].size())
//    {
//        deep[x] = 0;
//        return ;
//    }
    for(int i = 0; i < p[x].size(); i++)
    {
        int to = p[x][i];
        if(to == f) continue;
        deep[to] = deep[x] + 1; //求子节点的深度,是根据父节点转移而来的
        dp[1] += deep[to];
        dfs(to, x);
        cnt[x] += cnt[to];
    }
}
void dfs2(int x, int f)
{
    for(int i = 0; i < p[x].size(); i++)
    {
        int to = p[x][i];
        if(to == f) continue;
        dp[to] = dp[x] - cnt[to] + n - cnt[to];
        dfs2(to, x);
    }
}
int main()
{
    int u, v;
    while(~scanf("%lld", &n))
    {
        for(int i = 1; i <= n; i++) p[i].clear();
        memset(dp, 0, sizeof(dp));
        memset(deep, 0, sizeof(deep));
        memset(cnt, 0, sizeof(cnt));
        for(int i = 1; i < n; i++)
        {
            scanf("%d%d", &u, &v);
            p[u].push_back(v);
            p[v].push_back(u);
        }
        dfs(1, -1);
        dfs2(1,-1);
        for(int i = 1; i <= n; i++)
        {
            printf("%lld\n", dp[i]);
        }
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值