【树形DP】宝藏

Solution

挺有意思的一道树形DP,我理解了3个小时……
这道题目我们想一想后会发现大致要记录两个值:从i开始走再回到i的最大价值,从i开始不回到i的最大价值。 我们用方法一表示走某颗子树走下去再走回来,方法二表示走下去不回来。
我们用 f[i] 表示从i开始往子树走,再走回来的最大价值,(可以走很多棵子树), Max[i] 表示走每棵子树用方法二和方法一的价值之差,显然方法二更优秀,因为不用走回来,所以Max是正的,也就是说最后的答案是 f[root]+Max[root]
这样定义其实是为了求Ans,我们有且仅有一次不回来的机会,所以要用在方法二最能体现价值的地方,也就是说Max的定义是一个差值,两种方法的差值,所以我们选Max最大的那个子树走方法二,即不会来,这样就是最优解。(做完题目要理解这句话的含义,Max就是为了平衡f多减的值)

dis[u][v]=d

f[u]=w[u]+vumax(f[u]2d,0)

这个式子表示u往下走,假如再走回来的价值>0,当然是走,加上,不然就不走。
Max[u]=Max[v]d+f[v]v

Max[u]=Max[v]d+f[v](f[v]2d)v

走下去再走回来,假如没走过,代价是走一遍d,再加上走一些方法一和一条方法二的价值,这就是Max的定义,假如v走过方法一,那么把加上的减去即可,也就是- (f[v]2d)
这两个合起来就是
Max[u]=Max[v]d+f[v]max(f[v]2d,0)

要记住这个式子的含义,是一条d加上v的子树的价值
综上,我们得了50分
然后我们想如何换根,即O(1)转移
假设我们知道了u的f,Max,如何求v的?还是要平衡
这时的f,Max数组已经改变了含义,已经包括了了u上方的路径,即以u为根的f,Max
那么f[v]怎么求?它还差上方的u开始的这条路,因为f[u]可能包含从v向下走的路,我们要去掉,即 tmp=f[u]2dmax(0,f[v]2d) ,表示u即u上方的走去再回来的路径加上u、v连线的两倍,这时f[v]可以加上tmp,若tmp>0,否则不加
Max[v]怎么求?我们发现需要u不走v的Max,所以若Max[u]用到的是v,那么要用u的次大值,所以要在记录一个Max1数组表示次大值,只需求Max的时候同时更新即可,假设Max[u]不是用v来更新的,而且tmp>0那么平衡上面的需要Max[u]+d,+d是因为前面-d*2,不走回来只需要-d,所以+d来平衡,若tmp<0,那么上面的路没走,要用tmp+Max[u]+d,也是走过去再走回来的价值,跟上面比多了tmp?为什么,因为在上面的f[v]里加了tmp,这里没加,这样问题就解决了。

Problem

1678: 宝藏
时间限制: 1 Sec 内存限制: 256 MB
提交: 55 解决: 39
[提交][状态][讨论版]
题目描述
这里写图片描述

输入
这里写图片描述

输出
这里写图片描述

样例输入
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
提示
这里写图片描述

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=300010,M=600010;
ll f[N],Max[N],Max1[N],w[N],c[N],Head[N],n,tot;

struct Edge{
    ll v,d,next;
}edge[M];
void add(ll x,ll y,ll z)
{
    edge[++tot]=(Edge){y,z,Head[x]};
    Head[x]=tot;
}
void calc(ll u,ll x,ll v)
{
    if (x>Max[u])
    {
        Max1[u]=Max[u];
        Max[u]=x;
        c[u]=v;
    }
    else Max1[u]=max(Max1[u],x);
}
void dfs1(ll u,ll pre)
{
    f[u]=w[u];
    for (ll i=Head[u];i;i=edge[i].next)
    {
        ll v=edge[i].v,d=edge[i].d;
        if (v==pre) continue;
        dfs1(v,u);
        f[u]+=max(f[v]-2*d,0LL);
        calc(u,Max[v]-d+f[v]-max(0LL,f[v]-2*d),v);
    }   
}
void dfs2(ll u,ll pre)
{
    for (ll i=Head[u];i;i=edge[i].next)
    {
        ll v=edge[i].v,d=edge[i].d;
        if (v==pre) continue;
        ll tmp=f[u]-2*d-max(0LL,f[v]-2*d);
        if (tmp>0)
        {
            f[v]+=tmp;
            if (c[u]==v) calc(v,Max1[u]+d,u);
            else calc(v,Max[u]+d,u);
        }
        else 
        {
            //f[v]+=tmp;
            if (c[u]==v) calc(v,tmp+Max1[u]+d,u);
            else calc(v,tmp+Max[u]+d,u);
        }
        //f[v]+=max(0,f[u]-2*d-max(0,f[v]-2*d));
        //calc(v,c[u]==v?Max1[u]+d:Max[u]+d,u);
        dfs2(v,u);
    }
}
int main()
{
    scanf("%lld",&n);
    for (ll i=1;i<n;i++)
    {
        ll x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        add(x,y,z); add(y,x,z);
    }
    for (ll i=1;i<=n;i++) scanf("%lld",&w[i]);
    dfs1(1,0);  
    dfs2(1,0);
    for (ll i=1;i<=n;i++)
        printf("%lld\n",f[i]+Max[i]);   
    return 0;
}

下面是精简版

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=300010,M=600010;
ll f[N],Max[N],Max1[N],w[N],c[N],Head[N],n,tot;

struct Edge{
    ll v,d,next;
}edge[M];
void add(ll x,ll y,ll z)
{
    edge[++tot]=(Edge){y,z,Head[x]};
    Head[x]=tot;
}
void calc(ll u,ll x,ll v)
{
    if (x>Max[u])
    {
        Max1[u]=Max[u];
        Max[u]=x;
        c[u]=v;
    }
    else Max1[u]=max(Max1[u],x);
}
void dfs1(ll u,ll pre)
{
    f[u]=w[u];
    for (ll i=Head[u];i;i=edge[i].next)
    {
        ll v=edge[i].v,d=edge[i].d;
        if (v==pre) continue;
        dfs1(v,u);
        f[u]+=max(f[v]-2*d,0LL);
        calc(u,Max[v]-d+f[v]-max(0LL,f[v]-2*d),v);
    }   
}
void dfs2(ll u,ll pre)
{
    for (ll i=Head[u];i;i=edge[i].next)
    {
        ll v=edge[i].v,d=edge[i].d;
        if (v==pre) continue;
        ll tmp=f[u]-2*d-max(0LL,f[v]-2*d);
        f[v]+=max(0LL,tmp);
        calc(v,((c[u]==v)?Max1[u]:Max[u])+min(0LL,tmp)+d,u);
        dfs2(v,u);
    }
}
int main()
{
    scanf("%lld",&n);
    for (ll i=1;i<n;i++)
    {
        ll x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        add(x,y,z); add(y,x,z);
    }
    for (ll i=1;i<=n;i++) scanf("%lld",&w[i]);
    dfs1(1,0);  
    dfs2(1,0);
    for (ll i=1;i<=n;i++)
        printf("%lld\n",f[i]+Max[i]);   
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值