宝藏

题目描述
一棵n个点的树,到达一个点会获得这个点上的宝藏,每个宝藏都有一定的价值。经过每条边需要支付一定的过路费。每个点只有一个宝藏,但过路费每次都要交。求从每个点出发能得到的最大收益。
输入
输入文件为treasure.in。
第一行为一个正整数n。
接下来n-1行,每行三个整数x, y, z,描述一条边的两个端点x, y和过路费z。
最后一行n个数,表示每个点上宝藏的价值ai。
输出
输出文件为treasure.out。
输出n行,每行一个数。第i行表示从i出发的最大收益。
样例输入
6
1 2 1
2 3 3
3 4 36
3 6 13
3 5 2
6 8 9 10 13 1
样例输出
30
29
28
10
30
16
提示
对于20%的数据,满足n<=10。
对于50%的数据,满足n<=1000。
对于100%的数据,满足1<=n<= 3105 , 1<=z, ai<= 105

Solution

travel的升级版,以1为根往下做一遍,往上做一遍。
写的过程真是悲壮感人。。。(话说为什么联考里的dp全是树形的)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int n,x,y,z,tot;
int head[300005],Next[600005],to[600005],len[600005];
int a[300005];
ll f[300005],g[300005];//f回到i,g不回到i 
ll sf[300005],sg[300005]; 
ll son_down[300005],son_up[300005];
void add(int x,int y,int z)
{
    tot++;
    Next[tot]=head[x];
    to[tot]=y;
    len[tot]=z;
    head[x]=tot;
}
void dp(int k,int pre)
{
    f[k]=a[k];
    g[k]=a[k]; 
    ll s=0; 
    for(int i=head[k];i!=-1;i=Next[i]) 
    if(to[i]!=pre) 
    {
        dp(to[i],k);
        s=s+max(0LL,f[to[i]]-2*len[i]);//去当前子树or不去
    }
    son_down[k]=s; //从底向下做的时候,记录k儿子的信息 
    f[k]=s+a[k]; //势必要回到i 
    for(int i=head[k];i!=-1;i=Next[i]) 
    if(to[i]!=pre) 
    {
        ll left=s-max(0LL,f[to[i]]-2*len[i]); //其他子树还要去 
        g[k]=max(g[k],left+max(g[to[i]],f[to[i]])-len[i]+a[k]);
    }
}
void DP(int k,int pre)
{
    ll s=0,left=0;
    for(int i=head[k];i!=-1;i=Next[i]) 
    if(to[i]!=pre) s=s+max(0LL,f[to[i]]-2*len[i]);      
    s=s+max(0LL,sf[k]);
    for(int i=head[k];i!=-1;i=Next[i]) 
    if(to[i]!=pre) 
    {
        left=s-max(0LL,f[to[i]]-2*len[i]);
        son_up[to[i]]=max(son_up[to[i]],left-2*len[i]);
        sf[to[i]]=max(sf[to[i]],left-2*len[i]+a[to[i]]);
    }
    ll s1=0,s2=0;
    int p1=0,p2=0;
    for(int i=head[k];i!=-1;i=Next[i]) 
    if(to[i]!=pre) 
    {
        left=s-max(0LL,f[to[i]]-2*len[i])+g[to[i]]-len[i];
        if(left>s1) 
        {
            s2=s1;
            p2=p1;
            s1=left;
            p1=to[i];
        }
        else
        if(left>s2) 
        {
            s2=left;
            p2=to[i];
        }
    }
    left=s-max(0LL,sf[k])+sg[k];
    if(left>s1) 
    {
        s2=s1;
        p2=p1;
        s1=left;
        p1=k;
    }
    else
    if(left>s2) 
    {
        s2=left;
        p2=k;
    }
    for(int i=head[k];i!=-1;i=Next[i])
    if(to[i]!=pre) 
    {
        if(p1==to[i]) 
        {
            sg[to[i]]=max(sg[to[i]],s2-len[i]+a[to[i]]-max(0LL,f[to[i]]-2*len[i]));
        }
        else
        {
            sg[to[i]]=max(sg[to[i]],s1-len[i]+a[to[i]]-max(0LL,f[to[i]]-2*len[i]));
        }
    }
    for(int i=head[k];i!=-1;i=Next[i]) 
    if(to[i]!=pre) DP(to[i],k);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) head[i]=-1;
    for(int i=1;i<n;i++) 
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]);
        sf[i]=sg[i]=a[i];
    }
    dp(1,0);
    DP(1,0);
    for(int i=1;i<=n;i++) printf("%lld\n",max(max(sf[i]+f[i]-a[i],g[i]+son_up[i]),sg[i]+son_down[i]));
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值