CF 1084 D The Fair Nut and Strings 树形dp

给出一棵无根的树, 每个节点上有价值, 边上有花费, 每到一个点能得到这个点的价值, 每到一条边会减掉这个边的价值, 只有当前拿到的价值>花费才能走这条边, 在随意选择起点和终点的情况下, 问最多能得到多少价值.

思路: 这个题与那个Computer的树形dp很像, 无非是多了几个限定条件, 点有了价值, 边有了花费, 还是求最大价值.

Ⅰ首先将这个无根树固定一个根, 每个点就有了固定的父子关系. 这里是一个dfs.

Ⅱ 从叶子节点向根节点更新, 每个点保存最大值和次大值, 每个值都要记录其来源, 这是为了保证Ⅲ中能确定不会用自己更新自己.另外, 要注意每个点的最大值和次大值不仅包括儿子的贡献, 在初始时注意最大值应初始化为自己的点上权值.

Ⅲ 从根节点向叶子节点更新, 父亲的最大值/次大值只要不是由当前节点贡献, 那就要用来更新当前节点的最大值/次大值, 当然, 前提是这个值>=父亲→当前节点的距离.

这样, 每个点的最大值的最大值就是答案.

树形dp一步一步的写, 还是很好debug的.

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll, int> pli;
typedef pair<int, int> pii;

const int M = 3e5 + 5;
const int inf = 1e9 + 5;

ll w[M];
vector<int> edge[M];//无根树的边
int n;

vector<int> sons[M];
int fa[M];

void dfs(int now) {
    for (auto v:edge[now]) {
        if (fa[now] == v)continue;//无根树是双向边, 当然不能让它跑父节点
        fa[v] = now;
        sons[now].push_back(v);//这样就变成单向边了
        dfs(v);
    }
}


ll dp[M][2];
int source[M][2];
map<pii, ll> dis;//两个点之间的边长

pli dfs2(int now) {// 返回 最大值和最大值来源
    if (sons[now].empty()) {
        dp[now][0] = w[now];
        source[now][0] = now;
        dp[now][1] = -1;
        source[now][1] = -1;
        return make_pair(w[now], -1);
    }
    pli m1 = make_pair(w[now], now), m2 = make_pair(-1, -1);
    for (auto v:sons[now]) {
        pli tmp = dfs2(v);
        tmp.second = v;//这里返回的"来源"可能是"孙子", 要改成"儿子"
        tmp.first -= dis[make_pair(now, v)];
        if (tmp.first < 0)continue;
        tmp.first += w[now];
        if (tmp.first > m1.first) {
            m2 = m1;
            m1 = tmp;
        } else if (tmp.first > m2.first) {
            m2 = tmp;
        }
    }
    if (m1.first < 0) {
        dp[now][0] = -1;
        source[now][0] = -1;
        dp[now][1] = -1;
        source[now][1] = -1;
        return make_pair(-1, -1);
    }

    dp[now][0] = m1.first;
    source[now][0] = m1.second;
    dp[now][1] = m2.first;
    source[now][1] = m2.second;

    return m1;
}


void dfs3(int now) {
    if (now != 1) {
        for (int i = 0; i < 2; i++) {//用父节点的两个最大值更新
            if (source[fa[now]][i] == now)continue;// 如果这个值来自自己, 当然不能用.
            if (dp[fa[now]][i] > dis[make_pair(now, fa[now])]
                && dp[now][0] < dp[fa[now]][i] - dis[make_pair(now, fa[now])] + w[now]) {
                source[now][1] = source[now][0];
                dp[now][1] = dp[now][0];
                source[now][0] = source[fa[now]][i];
                dp[now][0] = dp[fa[now]][i] - dis[make_pair(now, fa[now])] + w[now];
            } else if (dp[fa[now]][i] > dis[make_pair(now, fa[now])]
                       && dp[now][1] < dp[fa[now]][i] - dis[make_pair(now, fa[now])] + w[now]) {
                source[now][1] = source[fa[now]][i];
                dp[now][1] = dp[fa[now]][i] - dis[make_pair(now, fa[now])] + w[now];
            }
        }
    }
    for (auto v:sons[now]) {
        dfs3(v);
    }
}


void solve() {
    memset(fa, -1, sizeof(fa));
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%I64d", &w[i]);
    }
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        ll diss;
        scanf("%d%d%I64d", &u, &v, &diss);
        dis[make_pair(u, v)] = dis[make_pair(v, u)] = diss;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    int bigBoss = 1;//规定一个根
    dfs(1);//转化为有根树
    dfs2(1);//从叶到根更新dp和source

//    for (int i = 1; i <= n; i++) {
//        printf("[%d]dp1:%I64d, s1:%d, dp:%I64d, s2:%d \n", i, dp[i][0], source[i][0], dp[i][1], source[i][1]);
//    }

    dfs3(1);//从根到叶更新dp和source
    ll ans = -inf;
    for (int i = 1; i <= n; i++) {
        ans = max(ans, dp[i][0]);
    }
    printf("%I64d\n", ans);
}


int main() {
    solve();
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值