https://vjudge.net/problem/ZOJ-3937
题意:
给出一棵根为1的树,每个点有点权(可能为负)。选出一条自上而下的路径,第 i i i个点(从上往下,1开始)计入的贡献为 i ∗ a [ i ] i*a[i] i∗a[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个点] 1∗a[1]+...k∗a[第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=K∗X+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
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
#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*/