bzoj3672 [Noi2014]购票(dp+斜率优化+点分治+cdq分治)

首先考虑如果是序列怎么做,比较明显的dp,dp[i]表示i走到1的最少花费,则
dp[i]=min{dp[j]+(dis[i]dis[j])p[i]+q[i]|j<i,dis[i]dis[j]<=li[i]} d p [ i ] = m i n { d p [ j ] + ( d i s [ i ] − d i s [ j ] ) ∗ p [ i ] + q [ i ] | j < i , d i s [ i ] − d i s [ j ] <= l i [ i ] }
我们自然想到斜率优化,我就不推式子了qaq。但是有两个限定条件决定j的取值范围,我们只好cdq分治维护凸包+二分查找最优斜率。推广到树上也是类似的,只不过我们这回要cdq分治结合点分治了。因为是一棵有根树,我们每次找到重心后,先分治处理此次分治子树的根(下文中的根都指此意义)所在的子树,然后用重心到根的路径上的点来更新重心的其他子树的点的答案(为了方便维护凸包,我们想只往里加点,于是先把子树的所有点按dis[i]-li[i]从大到小排序,而dis[now]是逐渐递减的,我们就可以只往里加点,而不删点了),再分治处理其他子树内部的影响。

注意维护这个下凸壳时是从右往左加点的…所以写着就比较的奇怪
复杂度 O(nlog2n) O ( n l o g 2 n )

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define ll long long
#define inf 1LL<<60
#define N 200010
#define pa pair<ll,int>
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,fa[N],sz[N],rt,sumsize,f[N],cnt,q[N];
ll w[N],pi[N],qi[N],li[N],dis[N],dp[N];
bool vis[N];pa a[N];
vector<int>son[N];
inline void dfs(int x){
    for(int i=0;i<son[x].size();++i){
        int y=son[x][i];dis[y]=dis[x]+w[y];dfs(y);
    }
}
inline void dfs2(int x){
    sz[x]=1;f[x]=0;
    for(int i=0;i<son[x].size();++i){
        int y=son[x][i];if(vis[y]) continue;
        dfs2(y);f[x]=max(f[x],sz[y]);sz[x]+=sz[y];
    }f[x]=max(f[x],sumsize-sz[x]);if(f[x]<f[rt]&&sz[x]>1) rt=x;//至少要分治多于一个点 
}
inline void dfs3(int x){
    a[++cnt]=make_pair(dis[x]-li[x],x);
    for(int i=0;i<son[x].size();++i){
        int y=son[x][i];if(vis[y]) continue;dfs3(y);
    }
}
inline bool cmp(pa a,pa b){return a.first>b.first;}
inline double slope(int k1,int k2){return (dp[k1]-dp[k2])*1.0/(dis[k1]-dis[k2]);}
inline void solve(int x,int Size){
    if(Size==1) return;
    rt=0;sumsize=Size;dfs2(x);int gp=rt;
    for(int i=0;i<son[gp].size();++i) vis[son[gp][i]]=1;//把gp的儿子都堵上 
    //cdq分治,先处理根x所在子树,注意把重心gp也划分到根x所在子树中
    solve(x,Size-sz[gp]+1);
    cnt=0;
    for(int i=0;i<son[gp].size();++i) dfs3(son[gp][i]);
    sort(a+1,a+cnt+1,cmp);int now=gp,h=1,t=0;//按dis[i]-li[i]从大到小给i排序。
    //用gp到根x的链上的点更新gp子树中的点的答案 
    for(int i=1;i<=cnt;++i){
        while(now!=fa[x]&&dis[now]>=a[i].first){
            while(h<t&&slope(q[t],now)>=slope(q[t-1],q[t])) t--;
            q[++t]=now;now=fa[now];
        }int l=1,r=t-1;if(h>t) continue;//注意判空 
        while(l<=r){//二分查找最优解 
            int mid=l+r>>1;
            if(slope(q[mid],q[mid+1])<pi[a[i].second]) r=mid-1;
            else l=mid+1;
        }int j=q[r+1];
        dp[a[i].second]=min(dp[a[i].second],dp[j]+(dis[a[i].second]-dis[j])*pi[a[i].second]+qi[a[i].second]);
    }for(int i=0;i<son[gp].size();++i) solve(son[gp][i],sz[son[gp][i]]);
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();int tt=read();f[0]=n+1;
    for(int i=1;i<=n;++i) dp[i]=inf;dp[1]=0;
    for(int i=2;i<=n;++i){
        fa[i]=read();son[fa[i]].push_back(i);w[i]=read();pi[i]=read();qi[i]=read();li[i]=read();
    }dfs(1);solve(1,n);
    for(int i=2;i<=n;++i) printf("%lld\n",dp[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值