题目链接: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;
}