题目大意,给定一棵有根树($1$号点是根),每个点有$5$个参数$fa,len,d,cst,unt$
$fa$表示点$x$的父节点编号,$d$表示$x$到父节点的距离。
在$x$可以花费$dis(x,y) imes unt_x+cst_x$的价格到达$x$的祖先$y$,其中$dis(x,y)$为$x$到$y$的距离,且必须满足$dis(x,y)leq len_x$,求每个点到根的最小代价。
先考虑如果树是一条链怎么做,显然只是一个可以斜率优化的递推式。
$F_i$表示从$i$出发的最小代价,$dep_i$表示$i$到根的距离
$F_i=min{F_j+cst_i(dep_i-dep_j)+unt_i}(dep_i-dep_jleq len_i)$
$F_i-cst_i imes dep_i-unt_i+cst_i imes dep_j=F_j$
令$X_k=dep_k,Y_k=F_k$,那么问题就出转化为了求斜率为$cst_i$的过$(X_j,Y_j)$之一的直线使得与$y$轴截距最小,由于有一个$len$的范围,所以没有办法直接扫一遍维护下凸壳,我们只能采用$CDQ$分治,考虑前半部分的点对后半部分的贡献,将后半部分的点按照$dep_x-len_x$降序排序,前半部分的点按照$X_k$降序,每次动态由$X_k$从大到小的顺序插入满足$dep_kgeq dep_x-len_x$的$X_k,Y_k$维护下凸壳,再在凸壳上进行二分即可。
思考如何把$CDQ$分治转到树上,其实只需要每次找到树分治区域的重心,优先处理重心靠近根一侧的部分,在暴力更新一遍重心,然后将重心的儿子的子树的点拿出来按照$dep_x-len_x$降序排序,再把重心在分治区内的所有祖先依次拿出来维护凸壳用来更新,就完成了在树上的$CDQ$分治。
最终复杂度为$O(Nlog^2N)$。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 400020 #define INF 4000000000000000ll using namespace std; const int BufferSize=1<<19; char buffer[BufferSize],*head,*tail; char Getchar(){ if(head==tail){ int l=fread(buffer,1,BufferSize,stdin); tail=(head=buffer)+l; } return *head++; } LL read(){ LL nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } void write(LL x){if(x>9) write(x/10);putchar(x%10+'0');} int n,m,sz[M],fs[M],to[M],nt[M],fa[M],tmp,root,mxs[M],top,S[M],sum,maxn,P[M]; LL F[M],dep[M],C[M],D[M],len[M],tot; bool vis[M]; double Y(int i){return F[i]*1.0;} double X(int i){return dep[i]*1.0;} bool conv(int t1,int t2,int t3){return (Y(t3)-Y(t2))*(X(t2)-X(t1))>(Y(t2)-Y(t1))*(X(t3)-X(t2));} void link(int x,int y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;} void init(int x){dep[x]+=dep[fa[x]];for(int i=fs[x];i!=-1;i=nt[i]) init(to[i]);} bool cmp(int x,int y){return dep[x]-len[x]>dep[y]-len[y];} LL getans(int i,int j){return F[j]+(dep[i]-dep[j])*C[i]+D[i];} void fdrt(int x){ sz[x]=1,mxs[x]=0; for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) fdrt(to[i]),mxs[x]=max(mxs[x],sz[to[i]]),sz[x]+=sz[to[i]]; mxs[x]=max(mxs[x],sum-sz[x]); if(mxs[x]<maxn) maxn=mxs[x],root=x; } void ins(int x){while(tot>1&&!conv(x,P[tot],P[tot-1])) tot--; P[++tot]=x;} void upd(int x){ if(!tot) return; int md,mx=tot,mi=2,res=0; F[x]=min(F[x],getans(x,P[1])); while(mi<=mx){ md=((mx+mi)>>1); if(getans(x,P[md])>getans(x,P[md-1])) mx=md-1; else mi=md+1,res=P[md]; } if(res) F[x]=min(F[x],getans(x,res)); } void dp(int x){S[++top]=x;for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) dp(to[i]);} void solve(int anc,int x){ vis[x]=true,tot=top=0; if(anc!=x) sum=sz[anc]-sz[x],maxn=M,fdrt(anc),solve(anc,root),tot=top=0; for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) dp(to[i]); for(int y=x;y!=anc;y=fa[y]){ if(dep[x]-dep[fa[y]]>len[x]) break; F[x]=min(F[x],getans(x,fa[y])); } sort(S+1,S+top+1,cmp),tot=0; for(int i=1,now=x;i<=top;upd(S[i]),i++){ while(now!=fa[anc]&&dep[now]>=dep[S[i]]-len[S[i]]) ins(now),now=fa[now]; } tot=top=0; for(int i=fs[x];i!=-1;i=nt[i]){ if(vis[to[i]]) continue; tot=top=0,maxn=M; sum=sz[to[i]],fdrt(to[i]),solve(to[i],root),tot=top=0; } } int main(){ n=read(),read(),sum=n,memset(fs,-1,sizeof(fs)); memset(F,0x3f,sizeof(F)),F[1]=0; for(int i=2;i<=n;i++){ fa[i]=read(),dep[i]=read(),link(fa[i],i); C[i]=read(),D[i]=read(),len[i]=read(); } init(1); maxn=M,fdrt(1),solve(1,root); for(int i=2;i<=n;i++) write(F[i]),putchar(' '); return 0; }