各位dalao的解法都好神啊。。。

这里给一种点分治的解法。

题目

链接

思路

首先斜率部分。

转移方程:

\(ans[x]=min(-dis[u]*p[x]+ans[u])+dis[x]*p[x]+q[x]\)

发现结果只与\(min()\)框内的部分有关,观察这个形式,发现是个一次函数,也就是说可以斜率优化。

dalao们对于推斜率式子这部分表示很不屑,但是我太菜了,所以还是写一下吧。

\(p=p[x]\),当\(u\)的结果比\(v\)更优且\(u\)\(v\)的儿子时:

\[ ---dis[u]*p+ans[u]<-dis[v]*p+ans[v]\\ -->(dis[u]-dis[v])*p>ans[u]-ans[v]\\ -->p>(ans[u]-ans[v])/(dis[u]-dis[v])(在此题中如果u是v的父亲,则dis[u]>dis[v],故符号不变)\\ \]

然后从根到当前点,维护一个斜率单调递增的栈,放张图感受一下:

以上算是补充了dalao们省略的部分。

下面就是点分了。

对于某一点而言,我们有一下流程更新它的子树的答案:

  1. 算出它所有祖先的ans
  2. 用它的祖先更新它子树中的点
  3. 用它自己更新子树中的点

在此过程中,维护上述所说的栈,然后二分找到图中橙色标注的这个最优点,更新答案。

至于\(l\)的限制该怎么处理呢?可以将它的祖先按照\(l​\)排序,利用一种类似归并排序的做法,将这一约束加上去,就可以了。

具体细节见代码吧。

ps:我代码中的顺序与图中相反(由儿子到父亲),不过这并不影响。

代码

#include<bits/stdc++.h> 
#define LL long long
#define M 200005
using namespace std;
int n,fa[M],h[M],tot;
LL p[M],q[M],l[M],dis[M],ans[M];
int qq[M],stk[M],top;
struct edge{int nxt,to;}G[M<<1];
void Add(int a,int b){
    G[++tot]=(edge){h[a],b};
    h[a]=tot;
}
int sz[M],rt,mx[M],tt;
bool vis[M];
void dfsrt(int x,int f){
    sz[x]=1;mx[x]=0;
    for(int i=h[x];i;i=G[i].nxt){
        int u=G[i].to;
        if(u==f||vis[u])continue;
        dfsrt(u,x);
        sz[x]+=sz[u];
        mx[x]=max(mx[x],sz[u]);
    }
    mx[x]=max(mx[x],tt-mx[x]);
    if(mx[x]<mx[rt])rt=x;
}
void dfssz(int x,int f){
    sz[x]=1;
    for(int i=h[x];i;i=G[i].nxt){
        int u=G[i].to;
        if(u==f||vis[u])continue;
        dfssz(u,x);
        sz[x]+=sz[u];
    }
}
bool cmp(int a,int b){return l[a]>l[b];}
double calcK(int u,int v){return (double)(ans[u]-ans[v])/(dis[u]-dis[v]);}
void dfsqq(int x,int f){
    qq[++qq[0]]=x;
    for(int i=h[x];i;i=G[i].nxt){
        int u=G[i].to;
        if(u==f||vis[u])continue;
        dfsqq(u,x);
    }
}
void solve(int x,int f){//f表示对于当前x来说,fa[x]及以上的祖先的影响已经算过了,不需要重复计算 
    dfssz(x,-1);//重新计算sz 
    vis[x]=1;
    if(x!=1&&!vis[fa[x]]){//计算一个点之前,它的祖先要先计算出来
        tt=sz[fa[x]];rt=0;
        dfsrt(fa[x],-1);
        solve(rt,f); 
    }
    qq[0]=0;dfsqq(x,fa[x]);
    sort(qq+1,qq+qq[0]+1,cmp);
    top=0;
    for(int i=1,now=fa[x];i<=qq[0];i++){//用当前点的父亲更新当前点的子树
        while(now!=fa[f]/*保证不超过这棵子树,保证复杂度*/&&l[qq[i]]<=dis[now]){
            while(top>1&&calcK(stk[top-1],stk[top])<=calcK(stk[top],now))top--;
            stk[++top]=now;now=fa[now];
        }
        if(!top)continue;
        int l=1,r=top;
        while(l!=r){
            int mid=(l+r)>>1;
            if(calcK(stk[mid],stk[mid+1])<(double)p[qq[i]])r=mid;
            else l=mid+1;
        }
        int j=qq[i];
        ans[j]=min(ans[j],ans[stk[l]]+(dis[j]-dis[stk[l]])*p[j]+q[j]);
    }
    for(int i=1;i<=qq[0];i++){
        if(qq[i]!=x&&l[qq[i]]<=dis[x]){
            int j=qq[i];
            ans[j]=min(ans[j],ans[x]+(dis[j]-dis[x])*p[j]+q[j]);
        }
    }
    for(int i=h[x];i;i=G[i].nxt){
        int u=G[i].to;
        if(!vis[u]&&u!=fa[x]){
            tt=sz[u];rt=0;
            dfsrt(u,x);
            solve(rt,u);
        }
    }
}
int t;
int main(){
    scanf("%d%d",&n,&t);
    mx[0]=1e9;
    memset(ans,0x3f,sizeof(ans));
    ans[1]=0;
    for(int i=2;i<=n;i++){
        LL ds;
        scanf("%d%lld%lld%lld%lld",&fa[i],&ds,&p[i],&q[i],&l[i]);
        Add(i,fa[i]);Add(fa[i],i);
        dis[i]=dis[fa[i]]+ds;
    }   
    for(int i=2;i<=n;i++)l[i]=dis[i]-l[i];
    tt=n;
    dfsrt(1,-1);
    solve(rt,1);
    for(int i=2;i<=n;i++)printf("%lld\n",ans[i]);
    return 0;
}