【点分治+CDQ思想+斜率优化】BZOJ3672 [NOI2014]购票

【题目】
原题地址
题目大意:一棵有根树,从一个城市 x x 到另一个城市y的花费为 dis(x,y)px+qx d i s ( x , y ) ∗ p x + q x ,同时要求 dis(x,y)<=limx,yx d i s ( x , y ) <= l i m x , y 是 x 的 祖 先 ,求每个城市到1号城市的最小花费。

【题目分析】
这个花费柿子一看就是斜率优化的形式,树上斜率优化之前也写过(CF932F),不过那题没有距离限制,只需要在子树中。这里我们可以考虑一下利用树分治的复杂度证明来做。

【解题思路】
看到这题的形式就想起前几天做的树上斜率优化,但是这题往更新的时候是有距离限制的,不过我们先不管它。
先写个方程: f[x]=min(f[x],f[y]+(dis[x]dis[y])p[x]+q[x]) f [ x ] = m i n ( f [ x ] , f [ y ] + ( d i s [ x ] − d i s [ y ] ) ∗ p [ x ] + q [ x ] ) ,同时 y y x的祖先, dis[x]dis[y]<=lim d i s [ x ] − d i s [ y ] <= l i m
整理一下得到 f[y]=dis[y]p[i]+f[x]dis[x]p[x]q[x] f [ y ] = d i s [ y ] ∗ p [ i ] + f [ x ] − d i s [ x ] ∗ p [ x ] − q [ x ] ,那么 f[x]dis[x]p[x]q[x] f [ x ] − d i s [ x ] ∗ p [ x ] − q [ x ] 表示过点 (dis[y],f[y]) ( d i s [ y ] , f [ y ] ) 的斜率为 p[x] p [ x ] 的直线的截距。
求最小值显然我们要维护的是一个下凸壳。
接下来因为距离的限制,如果暴力每次重构凸包显然不可行,但是不暴力我们似乎又无法得到凸包——我们需要一些奇技淫巧来做。
发现距离的限制是在一个范围内,可以考虑CDQ版的点分治。
找出分治重心后,我们先处理重心及其祖先的dp值,这样我们一会可以得到根到重心的凸包。
但是在构建凸包前,我们应该先考虑距离限制——可以将重心子树中所有点按能更新它的最深城市排序
枚举这些点的时候再从重心往上构建凸包即可。
总的时间复杂度是 O(nlog2n) O ( n l o g 2 n )

在写的过程中遇到了挺多问题的,比如应该预处理什么,凸包上二分的界之类的,这里也写一些重点:
1.CDQ过程中先解决当前重心以上的部分,具体实现上可以先找出当前子树x重心,然后将所有与重心相连的点标记已访问,确保不会重复经过,再调用solve处理以 x x 为根的子树的上半部分,这一部分的siz Ssiz[root]+1 S − s i z [ r o o t ] + 1 ,其中S表示找重心时的总点数(+1是因为包括了x,方便处理)

2.处理当前重心的子树时要再dfs出 dis[x]lim[x] d i s [ x ] − l i m [ x ] 的值,才能进行排序处理。

3.凸包上二分的时候还是最好加上边界判定,比如 mid==top m i d == t o p 的时候直接让 ret=top r e t = t o p 退出

4.CDQ还要记得别把后半部分忘记了。。。

【参考代码】

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

typedef long long LL;
const int N=2e5+10;
const LL INF=(1ll<<61);
int n,tot,cnt;
int fa[N],siz[N],head[N],mx[N],qs[N];
LL p[N],q[N],lim[N],dis[N],dp[N];
bool vis[N];

LL read()
{
    LL ret=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
    while(isdigit(c)){ret=ret*10+(c^48);c=getchar();}
    return f?ret:-ret;
}

void write(LL x)
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10ll);
    putchar(x%10^48);
}

struct Tway
{
    int v,nex;
    LL w;
};
Tway e[N];

void add(int u,int v,LL w)
{
    e[++tot]=(Tway){v,head[u],w};
    head[u]=tot;
}

struct Tnode
{
    int id;
    LL val;
};
Tnode a[N];

bool cmp(Tnode A,Tnode B)
{
    return A.val>B.val;
}

void dfs(int x)
{
    siz[x]=1;
    for(int i=head[x];i;i=e[i].nex)
    {
        int v=e[i].v;
        dis[v]=dis[x]+e[i].w;
        dfs(v);siz[x]+=siz[v];
    }
}

void dfs2(int x)
{
    a[++cnt].id=x;a[cnt].val=dis[x]-lim[x];
    for(int i=head[x];i;i=e[i].nex)
        if(!vis[e[i].v])
            dfs2(e[i].v);
}

void getroot(int x,int S,int &root)
{
    mx[x]=0;siz[x]=1;
    for(int i=head[x];i;i=e[i].nex)
    {
        int v=e[i].v;
        if(vis[v])
            continue;
        getroot(v,S,root);
        siz[x]+=siz[v];mx[x]=max(mx[x],siz[v]);
    }
    mx[x]=max(mx[x],S-siz[x]);
    if(mx[x]<mx[root] && siz[x]>1)
        root=x;
}

double slope(int x,int y)
{
    return (double)(dp[y]-dp[x])/(double)(dis[y]-dis[x]);
}

LL calc(int x,int y)
{
    return (LL)(dp[y]+(dis[x]-dis[y])*p[x]+q[x]);
}

void solve(int x,int S)
{
    if(S==1)
        return;
    int root=0,now;
    getroot(x,S,root);
    for(int i=head[root];i;i=e[i].nex)
        vis[e[i].v]=1;
    solve(x,S-siz[root]+1);

    cnt=0;
    for(int i=head[root];i;i=e[i].nex)
        dfs2(e[i].v);
    sort(a+1,a+cnt+1,cmp);

    now=root;
    int l,r,mid,pos,top=0;
    for(int i=1;i<=cnt;++i)
    {
//      for(int i=1;i<=top;++i)
//          printf("%d ",qs[top]);
//      puts("");
        while(now!=fa[x] && dis[a[i].id]-lim[a[i].id]<=dis[now])
        {
            while(top>1 && slope(qs[top],now)>=slope(qs[top-1],qs[top]))
                top--;
            qs[++top]=now;now=fa[now];
        }
        if(top)
        {
            l=1,r=top,pos=1;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(mid==top)
                {
                    pos=top;
                    break;
                }
                if(slope(qs[mid],qs[mid+1])<p[a[i].id])
                    r=mid-1,pos=mid;
                else
                    l=mid+1;
            }
            dp[a[i].id]=min(dp[a[i].id],calc(a[i].id,qs[pos]));
        }
//      printf("%d %d\n",top,pos);
    }

    for(int i=head[root];i;i=e[i].nex)
        solve(e[i].v,siz[e[i].v]);
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("BZOJ3672.in","r",stdin);
    freopen("BZOJ3672.out","w",stdout);
#endif 
    n=read();LL x=read();
    for(int i=2;i<=n;++i)
    {
        fa[i]=read();x=read();add(fa[i],i,x);
        p[i]=read();q[i]=read();lim[i]=read();
    }
    dfs(1);mx[0]=n+1;
    for(int i=2;i<=n;++i)
        dp[i]=INF;
    solve(1,siz[1]);
    for(int i=2;i<=n;++i)
        write(dp[i]),puts("");
    return 0;
}

【总结】
都写上面了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值