B - More Health Points(树形DP 斜率优化 斜率不单调)

https://vjudge.net/problem/ZOJ-3937

题意:

给出一棵根为1的树,每个点有点权(可能为负)。选出一条自上而下的路径,第 i i i个点(从上往下,1开始)计入的贡献为 i ∗ a [ i ] i*a[i] ia[i],求max(0,最大值)

解析:

定义 s u m [ p ] sum[p] sum[p]为从1到 p p p的权值之和, m u l [ p ] mul[p] mul[p]为从1到 p p p的贡献 1 ∗ a [ 1 ] + . . . k ∗ a [ 第 k 个 点 ] 1*a[1]+...k*a[第k个点] 1a[1]+...ka[k]

那么对于当前点 p p p,假设 q q q为第1个点的 f a t h e r father father,计算 d p [ p ] dp[p] dp[p]为:

d p [ p ] = m u l [ p ] − m u l [ q ] − d e p [ q ] ∗ ( s u m [ p ] − s u m [ q ] ) dp[p]=mul[p]-mul[q]-dep[q]*(sum[p]-sum[q]) dp[p]=mul[p]mul[q]dep[q](sum[p]sum[q])

由于 q q q为第一个点的 f a t h e r father father,所以可以弄一个虚点 0 = f a [ 1 ] , d e p [ 0 ] = 0 0=fa[1],dep[0]=0 0=fa[1]dep[0]=0
在这里插入图片描述

按照斜率优化的规范化简式子:

d p [ p ] = m u l [ p ] − m u l [ q ] − d e p [ q ] ∗ ( s u m [ p ] − s u m [ q ] ) dp[p]=mul[p]-mul[q]-dep[q]*(sum[p]-sum[q]) dp[p]=mul[p]mul[q]dep[q](sum[p]sum[q])

m u l [ q ] − d e p [ q ] ∗ s u m [ q ] = ( − s u m [ p ] ) ∗ d e p [ q ] + ( m u l [ p ] − d p [ p ] ) mul[q]-dep[q]*sum[q]=(-sum[p])*dep[q]+(mul[p]-dp[p]) mul[q]dep[q]sum[q]=(sum[p])dep[q]+(mul[p]dp[p])

d e p [ q ] ∗ s u m [ q ] − m u l [ q ] = ( s u m [ p ] ) ∗ d e p [ q ] + ( d p [ p ] − m u l [ p ] ) dep[q]*sum[q]-mul[q]=(sum[p])*dep[q]+(dp[p]-mul[p]) dep[q]sum[q]mul[q]=(sum[p])dep[q]+(dp[p]mul[p])

Y = K ∗ X + B Y=K*X+B Y=KX+B

条件一: X=dep[q]递增,满足

条件二: 截距为 d p [ p ] − m u l [ p ] dp[p]-mul[p] dp[p]mul[p]要求最大,需要斜率 K = s u m [ p ] K=sum[p] K=sum[p]递减,但是 s u m [ p ] sum[p] sum[p]递减不满足,所以无法删除开头的结点。只能维护一个凸包再三分得到极值。

由于是树形DP,所以往回回溯的时候,需要将凸包恢复至当前点塞入前。

可以这么处理:

    top=OriTop;
    S[InsertP]=OriValue;

恢复塞入前的top,并将当前点塞入的位置变回原来的值。

代码:

/*
 *  Author : Jk_Chen
 *    Date : 2020-08-15-16.35.12
 */
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define LD long double
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define per(i,a,b) for(int i=(int)(a);i>=(int)(b);i--)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pill pair<LL, int>
#define fi first
#define se second
void test(){cerr<<"\n";}
template<typename T,typename... Args>void test(T x,Args... args){cerr<<"> "<<x<<" ";test(args...);}
const LL mod=1e9+7;
const int maxn=1e5+9;
const int inf=0x3f3f3f3f;
LL rd(){ LL ans=0; char last=' ',ch=getchar();
    while(!(ch>='0' && ch<='9'))last=ch,ch=getchar();
    while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans; return ans;
}
#define rd rd()
/*_________________________________________________________begin*/


LL a[maxn];
vector<int>V[maxn];
int dep[maxn];
LL sum[maxn],mul[maxn];
LL dp[maxn];

LL Y(int p){return dep[p]*sum[p]-mul[p]; }
LL K(int p){return sum[p]; }
LL X(int p){return dep[p]; }
LL B(int q,int p){return Y(q)-K(p)*X(q); }
LL GetDp(int q,int p){return B(q,p)+mul[p]; }

int S[maxn],top; /// 斜率不单调左边的节点不能删除

void dfs(int p,int fa) {
    if(!p){
        S[++top]=0;
        dfs(1,0);
        return;
    }
    dep[p]=dep[fa]+1;
    sum[p]=sum[fa]+a[p];
    mul[p]=mul[fa]+a[p]*dep[p];


    int l=1,r=top;
    dp[p]=0;
    /// 凸包三分极值
    while(l<=r){
        if(l==r){
            dp[p]=max(dp[p],GetDp(S[l],p));
            break;
        }
        int mid=(l+r)>>1;
        if(GetDp(S[mid],p)>GetDp(S[mid+1],p)){
            dp[p]=max(dp[p],GetDp(S[mid],p));
            r=mid-1;
        }
        else{
            dp[p]=max(dp[p],GetDp(S[mid+1],p));
            l=mid+2; ///mid+1?
        }
    }

    int OriTop=top;///原来长度
    int InsertP;///插入位置
    int OriValue;///插入位置原来的值

    /// 维护凸包、删除结尾节点
    while(top>1 && (Y(S[top])-Y(S[top-1]))*(X(p)-X(S[top])) <=
        (X(S[top])-X(S[top-1]))*(Y(p)-Y(S[top])) ){
            top--;
    }
    InsertP=top+1;
    OriValue=S[top+1];
    S[++top]=p;

    for(auto u:V[p]){
        dfs(u,p);
    }

    /// 恢复删除的结点
    top=OriTop;
    S[InsertP]=OriValue;
}

int main() {
    int t=rd;
    V[0].pb(1);
    while(t--) {
        top=0;
        int n=rd;
        rep(i,1,n)a[i]=rd,V[i].clear();
        rep(i,2,n)V[rd].pb(i);
        dfs(0,-1);
        LL Ans=0;
        rep(i,1,n){
            //test(i,dp[i]);
            Ans=max(Ans,dp[i]);
        }
        printf("%lld\n",Ans);
    }
    return 0;
}

/*_________________________________________________________end*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值