欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[NOI2014] 购票

程序员文章站 2022-09-28 07:58:42
设f\[x\]为从x跑到1的最小花费,基本转移如下 $$ \left\{ \begin{aligned} f[1]&=0\\ f "x]&=\min_{dep[x] dep[y]\le l[x]} f[y]+p[x" +q[x]\\ &=p[x]dep[x]+q[x]+\min_{dep[x] de ......

设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