树的双直径 (树形dp,两条不相交链边权和的乘积最大)

3 篇文章 0 订阅
2 篇文章 0 订阅

树的双直径

题意

给一个n个节点的树,每条边都有权值,求出两条不相交链边权和的乘积最大
数据范围: n ≤ 4 ⋅ 1 0 5 ∣ c i ∣ ≤ 1 0 9 n\le 4\cdot 10^5\quad \left | c_i \right|\le10^9 n4105ci109
ps:51nod支持int128,边权可能为负
只有ans需要__int128,其他的都可以开long long

先用dfs求出每个节点,由v节点向下的链的最大权值和 d o w n [ v ] down[v] down[v],和可经过v节点的最大链权值和 b e s t [ v ] best[v] best[v](可以是当前子树内的一条链,也可以是从一个子树的最长链连到另一个子树的最长链之和)
然后用bfs遍历每个节点u和其所有的儿子v“截断”这条边,求两棵树内的最长链之积。
现在就有以u为根节点的一棵树,和以v为根节点的一棵树,我们记outside为以u为根节点的树中的最长链。
u p [ u ] up[u] up[u]为u向上可获得的最长链
p r e d o w n [ ] predown[] predown[]为u的儿子 d o w n [ ] down[] down[]的最大前缀
p p r e d o w n [ ] ppredown[] ppredown[]为u的儿子 d o w n [ ] down[] down[]的次大前缀
p r e b e s t [ ] prebest[] prebest[]为u的儿子 b e s t [ ] best[] best[]的最大前缀
s u f d o w n [ ] sufdown[] sufdown[]为u的儿子 d o w n [ ] down[] down[]的最大后缀
s s u f d o w n [ ] ssufdown[] ssufdown[]为u的儿子 d o w n [ ] down[] down[]的次大后缀
s u f b e s t [ ] sufbest[] sufbest[]为u的儿子 b e s t [ ] best[] best[]的最大后缀
通过这些数组,可以 O ( 1 ) O(1) O(1)求出outside对于任意u的儿子的所有组合,当枚举到儿子v时

outside = up[u] + max(predown[i - 1], sufdown[i + 1]);
outside = max(outside, predown[i - 1] + ppredown[i - 1]);
outside = max(outside, sufdown[i + 1] + ssufdown[i + 1]);
outside = max(outside, predown[i - 1] + sufdown[i + 1]);
outside = max(outside, max(prebest[i - 1], sufbest[i + 1]));

更新

up[v] = w + max(up[u], max(predown[i - 1], sufdown[i + 1]));
que.push(make_pair(v, u));

运用bfs儿子的前缀后缀处理算法
复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
#define int long long
#define ll __int128
using namespace std;
const int MAX_N = 400010;
inline void read(int &x){
    x = 0; int f = 1; char ch = getchar();
    while (!(ch >= '0' && ch <= '9')){if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}
int n, total;
int head[MAX_N];
ll best[MAX_N], down[MAX_N];
ll prebest[MAX_N], sufbest[MAX_N], predown[MAX_N], ppredown[MAX_N];
ll sufdown[MAX_N], ssufdown[MAX_N], up[MAX_N];

struct Edge{
    int to, next, w;
}edge[MAX_N * 2];

inline void AddEdge(int from, int to, int w){
    edge[total].to = to, edge[total].next = head[from], edge[total].w = w;
    head[from] = total++;
}

void dfs(int u, int p){
    ll Max = 0, MMax = 0;
    for (int i = head[u]; i != -1; i = edge[i].next){
        int v = edge[i].to;
        if (v == p) continue;
        dfs(v, u);
        int t = down[v] + edge[i].w;
        if (t > Max){
            MMax = Max;
            Max = t;
        }else if (t > MMax){
            MMax = t;
        }
        best[u] = max(best[u], best[v]);
    }
    down[u] = Max;
    best[u] = max(best[u], Max + MMax);
}
ll ans = -1;
void solve()
{
    queue<pair<int, int> > que;
    que.push(make_pair(1, 0));
    while (!que.empty()){
        pair<int, int> cur = que.front();
        int u = cur.first, p = cur.second;
        que.pop();
        vector<pair<int, int> > child;
        child.push_back(make_pair(0, 0));
        for (int i = head[u]; i != -1; i = edge[i].next){
            int v = edge[i].to;
            if (v != p) child.push_back(make_pair(v, edge[i].w));
        }
        int size = child.size();
        prebest[0] = predown[0] = ppredown[0] = 0;
        for (int i = 1; i < size; ++i){
            int v = child[i].first, w = child[i].second;
            prebest[i] = max(prebest[i - 1], best[v]);
            predown[i] = predown[i - 1], ppredown[i] = ppredown[i - 1];
            if (down[v] + w > predown[i]){
                ppredown[i] = predown[i];
                predown[i] = down[v] + w;
            }else if (down[v] + w > ppredown[i]){
                ppredown[i] = down[v] + w;
            }
        }
        sufbest[size] = sufdown[size] = ssufdown[size] = 0;
        for (int i = size - 1; i >= 1; --i){
            int v = child[i].first, w = child[i].second;
            sufbest[i] = max(sufbest[i + 1], best[v]);
            sufdown[i] = sufdown[i + 1], ssufdown[i] = ssufdown[i + 1];
            if (down[v] + w > sufdown[i]){
                ssufdown[i] = sufdown[i];
                sufdown[i] = down[v] + w;
            } else if (down[v] + w > ssufdown[i]){
                ssufdown[i] = down[v] + w;
            }
        }
        for (int i = 1; i < size; ++i){
            int v = child[i].first;
            ll outside = up[u] + max(predown[i - 1], sufdown[i + 1]);
            outside = max(outside, predown[i - 1] + ppredown[i - 1]);
            outside = max(outside, sufdown[i + 1] + ssufdown[i + 1]);
            outside = max(outside, predown[i - 1] + sufdown[i + 1]);
            outside = max(outside, max(prebest[i - 1], sufbest[i + 1]));
            if (outside * best[v] > ans) ans = outside * best[v];
        }
        for (int i = 1; i < size; ++i){
            int v = child[i].first, w = child[i].second;
            up[v] = w + max(up[u], max(predown[i - 1], sufdown[i + 1]));
            que.push(make_pair(v, u));
        }
    }
}
inline void write(ll x){if (x >= 10) write(x / 10); putchar((x % 10) + 48);}
signed main(){
    int n;
    read(n);
    memset(head, -1, sizeof(head));
    total = 0;
    for (int i = 1; i < n; ++i){
        int u, v, w;
        read(u), read(v), read(w);
        AddEdge(u, v, w);
        AddEdge(v, u, w);
    }
    dfs(1, 0);
    solve();
    for (int i = 0; i < total; i++) edge[i].w = -edge[i].w;
    memset(best, 0, sizeof(best));
    memset(down, 0, sizeof(down));
    memset(up, 0, sizeof(up));
    dfs(1, 0);
    solve();
    write(ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值