[NOI2014] 购票

设f[x]为从x跑到1的最小花费,基本转移如下
\[ \left\{ \begin{aligned} f[1]&=0\\ f[x]&=\min_{dep[x]-dep[y]\le l[x]} f[y]+p[x](dep[x]-dep[y])+q[x]\\ &=p[x]dep[x]+q[x]+\min_{dep[x]-dep[y]\le l[x]} f[y]-p[x]dep[y] \end{aligned} \right. \]
有斜率优化的影子
\[ f[x]=p[x]dep[x]+q[x]+(f[y]-p[x]dep[y])\\ f[y]=\underline{p[x]}dep[y]+\underline{f[x]-(p[x]dep[x]+q[x])} \]
于是在dp的过程中需要维护根到x父亲的一条链,二分求出被x接受的链的后缀,需要得到后缀上点(dep,f)的凸包,然后二分斜率求答案 。压入点、弹出点、计算后缀的凸包……显然这时间爆炸。

注意到这个后缀是可以分成若干段分别计算的,考虑对栈优雅地分块?时间复杂度\(O(n\sqrt n\log n)\)不太友好。

可以使用带撤销的二进制分组来肝,即用线段树来做一些完整区间(为方便处理,提前把树处理成满二叉树)的凸包,除了每层的最后一的满区间。

(其实这根树链剖分的做法已经很相似了啊)

#include <bits/stdc++.h>
#define ll long long 
using namespace std;

const int N=2e5+10;
const int M=N*8;

int n;
int head[N],to[N],lst[N];
int fa[N][20],dep[N],mxd;
ll wit[N],dis[N],P[N],Q[N],L[N],f[N];

int len=1,id[N*2];
namespace SGT {
    int bel[M],num[M],siz[M],cnt[25],tmp[N];
    vector<int> tre[M];
    bool hav[M];
#define ls (x<<1)
#define rs (x<<1|1)
    void build(int x,int l,int r) {
        num[x]=++tmp[bel[x]=id[r-l+1]];
        if(l==r) {tre[x].resize(1); return;}
        int mid=(l+r)>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
    }
    void upd(int x,int l,int r,int p,int k) {
        if(++siz[x]==r-l+1) ++cnt[bel[x]];
        if(l==r) {
            tre[x][0]=k;
            hav[x]=true;
            return;
        }
        int mid=(l+r)>>1;
        if(p<=mid) upd(ls,l,mid,p,k);
        else upd(rs,mid+1,r,p,k);
    }
    void del(int x,int l,int r,int p) {
        hav[x]=false;
        if(--siz[x]==r-l) --cnt[bel[x]];
        if(l==r) return;
        int mid=(l+r)>>1;
        if(p<=mid) del(ls,l,mid,p);
        else del(rs,mid+1,r,p);
    }
#define X(i) dis[i]
#define Y(i) f[i]
    inline ll calc(int y,int x) {return f[y]+P[x]*(dis[x]-dis[y])+Q[x];}
    inline bool cross(ll x1,ll y1,ll x2,ll y2) {return 1.0*x1*y2>=1.0*x2*y1;}
    inline void merge(vector<int> &a,vector<int> &l,vector<int> &r) {
        int top=0;
        for(auto i:l) tmp[++top]=i;
        for(auto i:r) {
            while(top>1&&cross(X(i)-X(tmp[top-1]),Y(i)-Y(tmp[top-1]), // 凸壳部分
                X(tmp[top])-X(tmp[top-1]),Y(tmp[top])-Y(tmp[top-1])))
                top--;
            tmp[++top]=i;
        }
        a.resize(top);
        for(int i=1; i<=top; ++i) a[i-1]=tmp[i];
    }
    void work(int x,int l,int r) {
        if(hav[x]) return;
        hav[x]=true;
        int mid=(l+r)>>1;
        work(ls,l,mid);
        work(rs,mid+1,r);
        merge(tre[x],tre[ls],tre[rs]);
    }
    inline ll calc(vector<int>& a,int x) {
        int l=0,r=a.size()-2,mid;
        ll ans=calc(a.back(),x),K=P[x];
        while(l<=r) {
            mid=(l+r)>>1;
            ans=min(ans,calc(a[mid],x));
            if(cross(1,K,X(a[mid+1])-X(a[mid]),Y(a[mid+1])-Y(a[mid]))) r=mid-1;
            else l=mid+1;
        }
        return ans;
    }
    ll query(int x,int l,int r,int L,int R,int X) {
        if(L<=l&&r<=R) {
            if(!hav[x]&&num[x]<cnt[bel[x]]) work(x,l,r);
            if(hav[x]) return calc(tre[x],X);
        }
        int mid=(l+r)>>1; ll ans=1e18;
        if(L<=mid) ans=query(ls,l,mid,L,R,X);
        if(mid<R) ans=min(ans,query(rs,mid+1,r,L,R,X));
        return ans;
    }
}

inline void add_edge(int x,int y,int w) {
    static int cnt=0;
    to[++cnt]=y;
    wit[cnt]=w;
    lst[cnt]=head[x];
    head[x]=cnt;
}
inline ll calc(int x) {
    int z=fa[x][0],y=x;
    for(int i=19; ~i; --i) 
        if(fa[y][i]&&dis[x]-dis[fa[y][i]]<=L[x]) y=fa[y][i];
    return SGT::query(1,1,len,dep[y],dep[z],x);
}
void pre(int x) {
    mxd=max(mxd,dep[x]=dep[fa[x][0]]+1);
    for(int i=1; (1<<i)<=dep[x]; ++i) fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x]; i; i=lst[i]) {
        dis[to[i]]=dis[x]+wit[i]; 
        pre(to[i]);
    }
}
void dfs(int x) {
    if(x!=1) f[x]=calc(x);
    SGT::upd(1,1,len,dep[x],x);
    for(int i=head[x]; i; i=lst[i]) dfs(to[i]);
    SGT::del(1,1,len,dep[x]);
}

int main() {
    ll val;
    scanf("%d%lld",&n,&val);
    for(int i=2; i<=n; ++i) {
        scanf("%d%lld%lld%lld%lld",&fa[i][0],&val,P+i,Q+i,L+i);
        add_edge(fa[i][0],i,val);
    }
    pre(1);
    for(int i=1; len<mxd; len<<=1,++i) id[len]=i;
    SGT::build(1,1,len);
    dfs(1);
    for(int i=2; i<=n; ++i) printf("%lld\n",f[i]);
    return 0;
}

代码大规模借鉴@i207M

posted @ 2019-05-24 16:58 nosta 阅读(...) 评论(...) 编辑 收藏