[NOI2014] 购票(https://www.luogu.com.cn/paste/f4nt6tiu)

O(N log^3N )

这道题的难点就在于要删除点,但是我们把树进行树链剖分,这样操作就只剩下插入了!

说一些小细节吧:

  1. 树剖的线段树,只有当size满了才求一遍凸包,归并两个子树的凸包,这样常数小。

  2. 树剖的线段树,如果它的区间跨过了两条重链,就不用维护它了

注意(个人犯的错误):

  1. 维护的是右下凸包-Thanks to zky

  2. 在找到最浅的祖先时,还是老老实实倍增吧

  3. 叉积会爆long long,用double

下面的代码,注释掉的是三分,没有注释掉的是二分,都对。

#define N 200005
const LL inf=1ll<<62;
int n;
int v[N],nx[N],head[N],cnte;
LL w[N];
il void adde(int uu,int vv,LL ww)
{
    v[++cnte]=vv,w[cnte]=ww,nx[cnte]=head[uu],head[uu]=cnte;
}
LL P[N],Q[N],L[N],f[N];
int dfn[N],ot[N],dk,ys[N],sz[N],hsn[N],fa[N],top[N];
int b[N][18]; // max 2^17
LL path[N];
void dfs(int x,int f)
{
    fa[x]=f,sz[x]=1;
    b[x][0]=f;
    for(ri i=1; i<=17&&b[x][i-1]; ++i) b[x][i]=b[b[x][i-1]][i-1];
    for(ri i=head[x]; i; i=nx[i])
    {
        path[v[i]]=path[x]+w[i];
        dfs(v[i],x);
        sz[x]+=sz[v[i]];
        if(sz[v[i]]>sz[hsn[x]]) hsn[x]=v[i];
    }
}
const int rt=1;
namespace seg
{

#define M N*4
#define ls (x<<1)
#define rs (x<<1|1)
#define gm int mid((l+r)/2)
#define X(i) (path[i])
#define Y(i) (f[i])
vector<int>bao[M];
int st[N],tp;
il bool cross(LL x1,LL y1,LL x2,LL y2)
{
    return (double)y1*x2<(double)y2*x1;
}
void build(vector<int> &a,vector<int> &l,vector<int> &r)
{
    tp=0;
    for(ri i=0; i<size(l); ++i) st[++tp]=l[i];
    for(auto i:r)
    {
        while(tp>1&&cross(X(i)-X(st[tp-1]),Y(i)-Y(st[tp-1]),X(st[tp])-X(st[tp-1]),Y(st[tp])-Y(st[tp-1])))
            --tp;
        st[++tp]=i;
    }
    a.resize(tp);
    for(ri i=1; i<=tp; ++i) a[i-1]=st[i];
}
LL calc(int a,int b)
{
    return f[a]+P[b]*(path[b]-path[a])+Q[b];
}
LL calc(vector<int> &a,int p)
{
    LL ans=inf;
    int l=0,r=size(a)-1;
    // while(r-l>3)
    // {
    //     int lm=l+(r-l)/3,rm=r-(r-l)/3;
    //     LL vl=calc(a[lm],p),vr=calc(a[rm],p);
    //     if(vl<=vr) r=rm;
    //     else l=lm;
    // }
    // for(ri i=l; i<=r; ++i) ans=min(calc(a[i],p),ans);
    LL k=P[p];
    while(l<=r)
    {
        int mid=(l+r)/2;
        ans=min(calc(a[mid],p),ans);
        if(mid==size(a)-1) r=mid-1;
        else if(cross(X(a[mid+1])-X(a[mid]),Y(a[mid+1])-Y(a[mid]),1,k)) l=mid+1;
        else r=mid-1;
    }
    return ans;
}
bool bj[M];
void updbj(int x,int l,int r,int ql,int qr)
{
    if(ql<=l&&r<=qr) bj[x]=1;
    if(l==r) return;
    gm;
    if(ql<=mid) updbj(ls,l,mid,ql,qr);
    if(qr>mid) updbj(rs,mid+1,r,ql,qr);
}
int sz[M];
void upd(int x,int l,int r,int p,int k)
{
    ++sz[x];
    if(l==r)
    {
        bao[x].pb(k);
        return;
    }
    gm;
    if(p<=mid) upd(ls,l,mid,p,k);
    else upd(rs,mid+1,r,p,k);
    if(bj[x]&&sz[x]==r-l+1)
        build(bao[x],bao[ls],bao[rs]);
}
LL query(int x,int l,int r,int ql,int qr,int k)
{
    if(ql<=l&&r<=qr)
    {
        return calc(bao[x],k);
    }
    LL ans=inf; gm;
    if(ql<=mid) ans=query(ls,l,mid,ql,qr,k);
    if(qr>mid) ans=min(query(rs,mid+1,r,ql,qr,k),ans);
    return ans;
}

}
void efs(int x,int tp)
{
    ys[dfn[x]=++dk]=x;
    top[x]=tp;
    if(hsn[x])
    {
        efs(hsn[x],tp);
        for(ri i=head[x]; i; i=nx[i])
            if(v[i]!=hsn[x]) efs(v[i],v[i]);
    }
    else seg::updbj(rt,1,n,dfn[top[x]],dfn[x]);
    ot[x]=dk;
}
LL calc(int x)
{
    int y=x,z=b[x][0];
    for(ri i=17; i>=0; --i)
        if(b[y][i]&&path[x]-path[b[y][i]]<=L[x]) y=b[y][i];
    LL ans=inf;
    while(top[z]!=top[y])
    {
        ans=min(seg::query(rt,1,n,dfn[top[z]],dfn[z],x),ans);
        z=fa[top[z]];
    }
    ans=min(seg::query(rt,1,n,dfn[y],dfn[z],x),ans);
    return ans;
}
void dfs(int x)
{
    if(x!=1) f[x]=calc(x);
    seg::upd(rt,1,n,dfn[x],x);
    for(ri i=head[x]; i; i=nx[i])
        dfs(v[i]);
}
void solve()
{
    path[0]=-1;
    dfs(1,0);
    efs(1,1);
    dfs(1);
}
signed main()
{
#ifdef M207
    freopen("in.in","r",stdin);
    // freopen("out.out","w",stdout);
#endif
    LL wst;
    in(n),in(wst);
    for(ri i=2,a; i<=n; ++i)
    {
        in(a),in(wst), adde(a,i,wst);
        in(P[i]),in(Q[i]),in(L[i]);
    }
    solve();
    for(ri i=2; i<=n; ++i) out(f[i]);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值