Codeforces 633 F The Chocolate Spree(树形dp,两条不相交链节点权值和最大)

题目链接:
Codeforces 633 F The Chocolate Spree
题意:
给一个n个节点的树和n−1条边,每个点有一个权值,从树中选择两条不相交的链(无公共节点)使得两条链上节点权值和最大?
数据范围:n≤105,value[i]≤109
分析:

先dfs处理出每个节点向其子树最多可以得到的链的最大权值和down[v](必须由v节点向下)和在子树中最长链的权值best[v](可以由一棵子树从下往上经过v节点然后转向另一棵子树,也可以是某棵子树的内部的最长链)。
然后借助队列遍历每个u和其儿子v“截断”这条边(以这条边为分界线),求的两棵树的内部最长链。这时就相当于把v节点和u以及u的其余儿子节点都分开了。我们先求u及u除了v以外的儿子节点所形成的树的最长链,记为outside。
并记up[u]为u向上可获得的最长链(不包括u节点),predown[]和
ppredown[]为u的儿子的down[]数组的前缀最大和次大,prebest[]为儿子的前缀最大,sufdown[],ssufdown[],sufbest[]分别为相应的后缀down[]数组最大和次大,best[]数组最大。
考虑outside可以获得的组合,当前枚举到第i个儿子:

up[u]+value[u]+max(predown[i−1],sufdown[i+1])
value[u]+predown[i−1]+sufdown[i+1]
value[u]+predown[i−1]+ppredown[i−1]
value[u]+sufdown[i+1]+ssufdown[i+1]
max(prebest[i−1],sufbest[i+1])
更新:

ans=max(ans,outside+best[v]),第i个儿子是v
up[v]=value[u]+max(up[u],max(predown[i−1],sufdown[i+1]));up[v]不包括v节点权值
借助队列和前后缀处理
时间复杂度:O(n)


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <climits>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int MAX_N = 100010;

int n, total;
int head[MAX_N];
ll value[MAX_N], 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;
}edge[MAX_N * 2];

inline void AddEdge(int from, int to)
{
    edge[total].to = to, edge[total].next = head[from];
    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);
        if (down[v] > Max) {
            MMax = Max;
            Max = down[v];
        } else if (down[v] > MMax) {
            MMax = down[v];
        }
        best[u] = max(best[u], best[v]); 
    }   
    down[u] = Max + value[u];
    best[u] = max(best[u], Max + MMax + value[u]); // best可以经过根节点
}

void solve()
{
    ll ans = 0;
    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<int> child;
        child.push_back(0);
        for (int i = head[u]; i != -1; i = edge[i].next) {
            int v = edge[i].to;
            if (v != p) child.push_back(v);
        }
        int size = child.size();
        // 记录down数组前缀最大和次大,best数组前缀最大
        prebest[0] = predown[0] = ppredown[0] = 0;
        for (int i = 1; i < size; ++i) {
            int v = child[i];
            prebest[i] = max(prebest[i - 1], best[v]);
            predown[i] = predown[i - 1], ppredown[i] = ppredown[i - 1];
            if (down[v] > predown[i]) {
                ppredown[i] = predown[i];
                predown[i] = down[v];
            } else if (down[v] > ppredown[i]) {
                ppredown[i] = down[v];
            }
        }
        // 记录down数组后缀最大和次大,best数组后缀最大
        sufbest[size] = sufdown[size] = ssufdown[size] = 0;
        for (int i = size - 1; i >= 1; --i) {
            int v = child[i];
            sufbest[i] = max(sufbest[i + 1], best[v]);
            sufdown[i] = sufdown[i + 1], ssufdown[i] = ssufdown[i + 1];
            if (down[v] > sufdown[i]) {
                ssufdown[i] = sufdown[i];
                sufdown[i] = down[v];
            } else if (down[v] > ssufdown[i]) {
                ssufdown[i] = down[v];
            }
        }
        // 遍历每个儿子,并且每个儿子的best是一条链
        for (int i = 1; i < size; ++i) {
            int v = child[i];
            ll outside = up[u] + value[u] + max(predown[i - 1], sufdown[i + 1]);
            outside = max(outside, value[u] + predown[i - 1] + ppredown[i - 1]);
            outside = max(outside, value[u] + sufdown[i + 1] + ssufdown[i + 1]);
            outside = max(outside, value[u] + predown[i - 1] + sufdown[i + 1]);
            outside = max(outside, max(prebest[i - 1], sufbest[i + 1]));
            ans = max(ans, outside + best[v]);
        }
        // 更新儿子的up并将儿子压进队列
        for (int i = 1; i < size; ++i) {
            int v = child[i];
            up[v] = value[u] + max(up[u], max(predown[i - 1], sufdown[i + 1]));
            que.push(make_pair(v, u));
        }       
    }
    printf("%lld\n", ans);
}

int main()
{
    while (~scanf("%d", &n)) {
        for (int i = 1; i <= n; ++i) { scanf("%lld", &value[i]); }
        memset(head, -1, sizeof(head));
        total = 0;
        for (int i = 1; i < n; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            AddEdge(u, v);
            AddEdge(v, u);
        }
        memset(best, 0, sizeof(best));
        memset(down, 0, sizeof(down));
        memset(up, 0, sizeof(up));
        dfs(1, 0);
        solve();
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值