loj2249/洛谷P2305/bzoj3672 购票 斜率优化+点分治

DP方程显然: fi=min(fj+(disidisj)pi+qi) f i = m i n ( f j + ( d i s i − d i s j ) ∗ p i + q i ) ,dis是根到每个点的距离。
假设j的深度比k大且j这个决策比k更优,那么就有:

fj+dipidjpi+qi<fk+dipidkpi+qi f j + d i p i − d j p i + q i < f k + d i p i − d k p i + q i

fjfkdjdk<pi f j − f k d j − d k < p i

记这个斜率式为 slop(j,k) s l o p ( j , k ) ,如果存在i的深度大于j,j的深度大于k且 slop(i,j)<slop(j,k) s l o p ( i , j ) < s l o p ( j , k ) ,那么j一定不是最优决策。(因为如果 pi>slop(j,k) p i > s l o p ( j , k ) ,则i比较优,否则k比较优),所以我们要维护一个上凸壳。

考虑用点分治来实现树上斜率优化,假设当前在处理一个以now为根的子树。
1.找到重心x。
2.递归处理包含now的那个连通块带上x
3.将子树x中,除x以外的点,按照其一趟车可以到达的最小深度,从大到小排序。
4.依次处理这些点,每次把新的可以一趟车到达的点加入凸壳中,单调栈维护一下。然后二分答案寻找当前这个点的最优决策。
5.递归处理x的子树。

复杂度是 O(nlog2n) O ( n l o g 2 n ) 的。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read() {
    LL q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
    return q;
}
#define RI register int
typedef double db;
const int N=200005;
int tot,n,t,rt,mx,js;
int h[N],ne[N],to[N],fa[N],sz[N],vis[N],P[N];
LL w[N],p[N],q[N],l[N],dis[N],f[N];

int st[N],top;db sl[N];
db slope(int x,int y) {return (db)(f[y]-f[x])/(db)(dis[y]-dis[x]);}
void ins(int x) {
    while(top>=2&&sl[top-1]<=slope(st[top],x)) --top;
    st[++top]=x,sl[top]=-1e18,sl[top-1]=slope(st[top-1],st[top]);
}
int query(db num) {
    int l=1,r=top,mid,re;
    while(l<=r) {
        mid=(l+r)>>1;
        if(sl[mid]<=num) re=mid,r=mid-1;
        else l=mid+1;
    }
    return st[re];
}

void getrt(int x,int SZ) {
    sz[x]=1;int bl=0;
    for(RI i=h[x];i;i=ne[i])
        if(!vis[to[i]])
            getrt(to[i],SZ),sz[x]+=sz[to[i]],bl=max(bl,sz[to[i]]);
    bl=max(bl,SZ-sz[x]);
    if(bl<=mx) rt=x,mx=bl;
}
void dfs(int x) {
    P[++js]=x;
    for(RI i=h[x];i;i=ne[i]) if(!vis[to[i]]) dfs(to[i]);
}
bool cmp(int x,int y) {return dis[x]-l[x]>dis[y]-l[y];}
void work(int now,int SZ) {
    if(SZ==1) return;
    mx=1e9,getrt(now,SZ);int x=rt,kmx=mx;
    for(RI i=h[x];i;i=ne[i]) vis[to[i]]=1,SZ-=sz[to[i]];
    work(now,SZ);
    js=0;for(RI i=h[x];i;i=ne[i]) dfs(to[i]);
    sort(P+1,P+1+js,cmp);

    int j=x;top=0;
    for(RI i=1;i<=js;++i) {
        int y=P[i];
        while(j!=fa[now]&&dis[j]>=dis[y]-l[y]) ins(j),j=fa[j];
        if(top) {
            int k=query(p[y]);
            f[y]=min(f[y],f[k]+(dis[y]-dis[k])*p[y]+q[y]);
        }
    }
    for(RI i=h[x];i;i=ne[i]) work(to[i],sz[to[i]]);
}

void add(int x,int y,int z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void getdis(int x)
    {for(RI i=h[x];i;i=ne[i]) dis[to[i]]=dis[x]+w[i],getdis(to[i]);}
int main()
{
    int z;n=read(),t=read();
    for(RI i=2;i<=n;++i) {
        fa[i]=read(),z=read(),add(fa[i],i,z);
        p[i]=read(),q[i]=read(),l[i]=read(),f[i]=LLONG_MAX;
    }
    getdis(1);work(1,n);
    for(RI i=2;i<=n;++i) printf("%lld\n",f[i]);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值