树上子链(换根dp练习)

这个题不需要换根dp,但是想当作一个换根dp的练习好了,以免以后碰到这种换根题目

链接:https://ac.nowcoder.com/acm/contest/4462/B

题目描述

给定一棵树 T ,树 T 上每个点都有一个权值。

定义一颗树的子链的大小为:这个子链上所有结点的权值和 。

请在树 T 中找出一条最大的子链并输出。

题目思路:

        (如果想AC这道题的话,直接dp出每个点的最大次大子链,就行了,因为最后答案一定是某个点的最大次大之和)

         (假如我们换一下题目,要求输出树上每个点包含这个点的最大链,这就必须换根了)

       涉及到换根dp了的话,首先我们假设1为根 打一个dp处理出来每个点的最大次大子链值。

        dp[i][0]表示i节点往下的最大子链值,dp[i][1]表示i节点往下的次大子链值。

       接下来就是换根了,假设当前某个点为根的话,那么包含他的最长链值,必定会包含他的儿子中最大值子链(因为对于这个点往外延伸出链的话,要么往下走,要么往上走,但是儿子中有一根最大的子链为什么不选呢,这里的子链可以为空)。

       假如对于每个点往外延申链的话,怎么延伸呢,首先最大子链肯定选了,次大子链和往父亲方向走的链是不是挑一个最大的就行了。

       这是又需要一个dp_up记录,包含这个点,往1的方向延伸的最大值。

       怎么转移?

       假如当前要求的点是x,他的父亲是fa

       如果当前的点是他父亲的最大子链,那么,就要拿dp_up[fa]和dp[fa][1]也就是次大值比较大小转移

即                        dp_up[x] = max(0ll, max(dp_up[fa],dp[fa][1]) ) + f[x];

       如果这个不是父亲的最大子链,那么,就要拿dp_up[fa]和dp[fa][0]也就是最大值比较大小转移

即                       dp_up[x] = max(0ll, max(dp_up[fa],dp[fa][0]) ) + f[x];

      对于每个点的结果的话,就是,首先最大子链选了,再在次大子链值和dp_up[fa]中选一个就行了。

            dp2记录答案   dp2[x] = dp[x][0] + max(dp[x][1] , dp_up[fa]) - f[x];

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MAXN = 1e5+5;
vector<int>v[MAXN];
const ll inf = 1ll<<62;
ll f[MAXN];
ll dp[MAXN][2];
ll idx_Max[MAXN];
ll dp2[MAXN],dp_up[MAXN];
void dfs(ll x,ll fa)
{
    ll len = v[x].size();
    ll Max1 = -inf,idx = x;
    ll Max2 = -inf;
    for(ll i=0;i<len;i++){
        ll to = v[x][i];
        if(to == fa)continue;
        dfs(to,x);
        if(dp[to][0]>Max1){
            Max1 = dp[to][0];
            idx = to;
        }
    }
    for(ll i=0;i<len;i++){
        ll to  = v[x][i];
        if(to == fa )continue;
        if(dp[to][0]>Max2 && to!=idx){
            Max2 = dp[to][0];
        }
    }
    dp[x][0] = max(0ll,Max1)+f[x];
    idx_Max[x]=idx;
    dp[x][1] = max(0ll,Max2)+f[x];
}
void dfs2(ll x,ll fa)
{
    ll len = v[x].size();
    if(x != 1){
        if(idx_Max[fa] == x){
                //cout<<" +++++ "<<x<<endl;
            dp_up[x] = max(0ll, max(dp_up[fa],dp[fa][1]) ) + f[x];
        }
        else{
            dp_up[x] = max(0ll, max(dp_up[fa],dp[fa][0]) ) + f[x];
        }
    }
    dp2[x] = dp[x][0] + ( max(dp[x][1],dp_up[x]) - f[x] ) ;
    for(ll i=0;i<len;i++){
        ll to = v[x][i];
        if(to == fa)continue;
        dfs2(to,x);
    }
}
int main()
{
    ll n;
    cin>>n;
    for(ll i=1;i<=n;i++){
        cin>>f[i];
    }
    for(ll i=1;i<n;i++){
        ll a,b;
        cin>>a>>b;
        v[a].push_back(b);
        v[b].push_back(a);
    }

    dfs(1,1);
    dp_up[1] = f[1];
    dfs2(1,1);
    for(ll i=1;i<=n;i++){
        cout<<i<<" : "<<dp[i][0]<<" + "<<dp[i][1]<<" * "<<idx_Max[i]<<" * "<<dp_up[i]<<endl;
    }

    ll Max =-inf;
    for(ll i=1;i<=n;i++){
        Max = max(Max,dp2[i]);
    }
    cout<<Max<<endl;
}
/*

5
-2 -1 -1 -2 -3
1 2
2 3
2 4
2 5
*/

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值