[树形DP] 最长乘积链

题目

1.最长乘积链 - 蓝桥云课 (lanqiao.cn)

初始思路

对问题进行分析,对每个点dfs去求走不同路的最远距离与次远距离求乘积,时间复杂度为O(n^2)

看了答案怎么弄的优化

解题思路

总的来说

预处理(对每个结点的信息进行统计),预处理完后对所有节点遍历去求乘积最大值

在确定树的拓扑结构后单独求一个节点的最远距离时,会在该树上去比较得到如下路径:

  1. 从当前节点往下,直到子树中某个节点的最长路径。
  2. 从当前节点往上走到其父节点,再从其父节点出发且不回到该节点的最长路径。

也就是先向上走与先向下走。两个方向上的最长路径相乘就是答案。

每个合法节点能贡献二个乘积,向下走的最远距离乘次远距离和向上走的最远距离乘向下走的最远距离。

处理次大值的原因是:

当我们求向上走的最大值时,原理为我到父结点的距离加上父结点到别的点的最远距离,如果父结点到别的点的最远距离经过我时则不能使用,此时就得变成我到父结点的距离加上父结点到别的点的次远距离。因为别一个乘积为从父到根的距离不一定是最优的。

树形DP

我们在此引入 树形DP 解题:

  1. 指定任意一个根节点。
  2. 一次dfs 遍历,统计出每个点向下走的最大值与次大值,以及最大值下去的方向与次大值下去的方向,最大值和次大值走的不是同一方向。
  3. 一次 dfs 遍历,统计出先向上走的最大值。

最大值与次大值更新原理为:当前结点到子节点的距离加上子节点到叶子结点的最远距离,我们对这些子结点求最大值与次大值即可

整体思路

标注:

  • 如何记录方向:记录以 i 出发的最近子节点是什么

代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 100010;
vector<pair<int, int>> g[N];
int d1[N], d2[N], up[N], p1[N], p2[N];
int n, m, k;

void dfs1(int u, int f) {
    for (auto v : g[u]) {
        if (v.first == f) continue;
        dfs1(v.first, u);  //先到叶子后转移
        if (d1[v.first] + v.second >= d1[u]) {  //叶子的d1d2都是0,如果可转移则相加转移
            d2[u] = d1[u];
            p2[u] = p1[u];
            d1[u] = d1[v.first] + v.second;
            p1[u] = v.first;
        } else if (d1[v.first] + v.second > d2[u]) {
            d2[u] = d1[v.first] + v.second;
            p2[u] = v.first;
        }
    }
}

void dfs2(int u, int f) {
    for (auto v : g[u]) {
        if (v.first == f) continue;
        if (p1[u] == v.first)
            up[v.first] = v.second + max(up[u], d2[u]);
        else
            up[v.first] = v.second + max(up[u], d1[u]);
        dfs2(v.first, u);
    }
}

int main() {
    int t = 1;
    for (int zu = 1; zu <= t; zu++) {
        cin >> n;
        for (int i = 1; i <= n - 1; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            g[a].push_back(make_pair(b, c));
            g[b].push_back(make_pair(a, c));
        }
        dfs1(1, -1);
        dfs2(1, -1);
        long long res = 0;
        for (int i = 1; i <= n; i++) {
            res = max(res, max(1LL * d1[i] * d2[i], 1LL * d1[i] * up[i]));
        }
        cout << res << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值