2016年浙江省赛B ZOJ 3937 More Health Points(树上dfs+斜率优化+可持久化维护凸壳)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5546

 

题目大意:给一棵有向树,任选一节点i,从j节点出去,权值为val[i]*1+val[i+1]*2+......+val[i+j-1]*j,也可以选择不进入,求最大权值。

 

题目思路:首先先考虑如果不在树上,如何解决此题。对于一个序列,求val[i]*1+val[i+1]*2+......+val[i+j-1]*j,若定义f[i]=val[1]*1+val[2]*2+......+val[i]*i,sum[i]=val[1]+val[2]+......+val[i],假设对于i来说,[j+1,i]这一区间是最优解,定义dp[i]为以i为终点的最优解,则dp[i]=f[i]-f[j]-j*(sum[i]-sum[j])。将该式展开得到dp[i]=f[i]-f[j]-j*sum[i]+j*sum[j],移项可得f[j]-j*sum[j]=-j*sum[i]+f[i]-dp[i]

设j为x,y为f[j]-j*sum[j],斜率为-sum[i],然后维护一个下凸壳,由下凸壳性质可知,其斜率保持不递减,存在单调性,同时又由线性规划知识,f[i]-dp[i]为截距,当截距最小时dp[i]最大,所以我们需要找到第一个大于-sum[i]的点,该点即满足要求的点。对于维护下凸壳,由于这是一个树上的斜率优化,序列中的数字在不断地改变,需要维护的点也都在改变,所以不能采用简单的单调队列进行维护,需要进行可持久化。首先可以发现,由于下凸壳中的点斜率保持递增,而每个点到i点的斜率又在递减,而当点的斜率大于到i点的斜率时,这个点就后面那个点就是下凸壳里面的点,如图所示:

然而他不能直接就改序列, 因为当dfs回溯时它需要回到原来的状态再继续新的征程,所以就需要满足可持久化需求,也就是需要将当前要插入的凸壳的位置和值都记下来,先改完然后再变回来。这里由于刚才说的性质也是具有单调性的,所以也可以二分实现。

 

以下是代码:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
const int MAXN = 1e5+5;
ll val[MAXN],ans,x[MAXN],y[MAXN];
int n,fa[MAXN],qh,qt,q[MAXN];
vector<int>v[MAXN];

void dfs(int u,int dep,ll sum,ll f){
    x[u]=dep,y[u]=f-dep*sum;
    int l=qh,r=qt-1,pos=qt;
    while(l<=r){
        int mid=(l+r)>>1;
        if((y[q[mid+1]]-y[q[mid]])>=-sum*(x[q[mid+1]]-x[q[mid]])){
            pos=mid;
            r=mid-1;
        }
        else{
            l=mid+1;
        }
    }
    ans=max(ans,-x[q[pos]]*sum-y[q[pos]]+f);
    l=qh,r=qt-1,pos=qt+1;
    while(l<=r){
        int mid=(l+r)>>1;
        if((y[q[mid+1]]-y[q[mid]])*(x[u]-x[q[mid]])>=(x[q[mid+1]]-x[q[mid]])*(y[u]-y[q[mid]])){
            pos=mid+1;
            r=mid-1;
        }
        else l=mid+1;
    }
    int oldval=q[pos],oldqt=qt;
    qt=pos,q[pos]=u;
    int len=v[u].size();
    rep(i,0,len-1){
        int y=v[u][i];
        dfs(y,dep+1,sum+val[y],f+(dep+1)*val[y]);
    }
    q[pos]=oldval,qt=oldqt;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        ans=0;
        scanf("%d",&n);
        rep(i,1,n)scanf("%lld",&val[i]),v[i].clear();
        rep(i,2,n){
            scanf("%d",&fa[i]);
            v[fa[i]].push_back(i);
        }
        qh=qt=1;q[1]=0;
        dfs(1,1,val[1],val[1]);
        printf("%lld\n",ans);
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值