CF Bear and Tree Jumps树形DP

本文介绍了一种使用树形动态规划解决树上节点间特定距离计算问题的方法。通过一次深度优先搜索(DFS),实现了对每个节点与其子节点间距离的有效计算,并在此基础上计算出了所有节点对之间的指定类型距离。

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

树形DP

题意:

​ 有一棵n个节点的树,现在定义一个函数:s(i,j),i<jij 距离是根据一个跳的次数而定,一个人从i节点每次最多跳k次,而其跳的次数便是距离。

思路:

​ 一般在树上计算距离或者其它运算的问题,很难离开树形dp的范畴,但是关键在于如何转化问题为结果。

​ 这里说i < j ,其实意味着i到j只需走一次即可。数据范围是200000,暴力枚举每一个节点是不行的,那么可不可以直接一次dfs求出想要的答案?对于一棵树而言,其中的某个节点有一个或者多个儿子,dp问题是 从子结构到最优解,那么能不能先只求出一个非叶子节点的儿子之间的所有距离,可以的。

​ 分析距离的计算方式发现是k的倍数会多跳一步,对于一个节点来说,其某一个儿子树到这个节点的其它儿子树的距离有两种情况:1 . 倍数 2. 除以k有余数的情况。 对于第一种直接保存在一个数组中,重点在于重复保存,这样可以直接当做跳一次就行,比如:k = 2的时候有一个儿子节点的到这个节点的距离是4,但是我们不直接拿着4/2而是保存两次倍数即可,算的时候直接做乘法,求出当前儿子树的倍数个数乘以父亲节点和已经算过的其它父亲节点儿子的个数,当然还要加上其它父亲的儿子节点的倍数关系乘以当前儿子的个数。

​ 那么还有一种情况是不是倍数关系但是到当前节点的距离加上到其儿子节点的距离大于k,那么需要加一次计算即可。

​ 当算出一个儿子树就与父亲节点合并起来,最终的答案就是解。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;
const int maxn = 200005;

int n,k;
LL dp[maxn][6],ans;
LL segment[maxn],sonNumber[maxn];
vector<int>G[maxn];

void dfs(int u,int fa)
{
    sonNumber[u] = 1;
    dp[u][0] = 1;
    int Size = G[u].size();
    for(int i = 0;i < Size; i++) {
        int to = G[u][i];
        if(to == fa) continue;
        dfs(to,u);
        ans += segment[to]*sonNumber[u] + segment[u]*sonNumber[to];
        for(int i = 0;i < k; i++) {
            for(int j = 0;j < k; j++) {
                ans += dp[u][i]*dp[to][j];
                if(i+j >= k)
                    ans += dp[u][i]*dp[to][j];
            }
        }
        sonNumber[u] += sonNumber[to];
        segment[u] += segment[to];
        segment[u] += dp[to][k-1];
        for(int i = 0;i < k; i++)
            dp[u][(i+1)%k] += dp[to][i];
    }
}

int main()
{
    //freopen("in.txt","r",stdin);

    scanf("%d%d",&n,&k);
    for(int i = 1;i < n; i++) {
        int e,s;
        scanf("%d%d",&e,&s);
        G[e].push_back(s);
        G[s].push_back(e);
    }
    ans = 0;
    dfs(1,0);
    printf("%I64d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值