zoukankan      html  css  js  c++  java
  • [NOI2014]购票(斜率优化+线段树)

     题目描述

    今年夏天,NOI在SZ市迎来了她30周岁的生日。来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会。

    全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接。为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号。其中SZ市的编号为 1。对于除SZ市之外的任意一个城市 v,我们给出了它在这棵树上的父亲城市 fv 以及到父亲城市道路的长度 sv。

    从城市 v 前往SZ市的方法为:选择城市 v 的一个祖先 a,支付购票的费用,乘坐交通工具到达 a。再选择城市 a 的一个祖先 b,支付费用并到达 b。以此类推,直至到达SZ市。

    对于任意一个城市 v,我们会给出一个交通工具的距离限制 lv。对于城市 v 的祖先 a,只有当它们之间所有道路的总长度不超过 lv 时,从城市 v 才可以通过一次购票到达城市 a,否则不能通过一次购票到达。对于每个城市 v,我们还会给出两个非负整数 pv,qv 作为票价参数。若城市 v 到城市 a 所有道路的总长度为 d,那么从城市 v 到城市 a 购买的票价为 dpv+qv。

    每个城市的OIer都希望自己到达SZ市时,用于购票的总资金最少。你的任务就是,告诉每个城市的OIer他们所花的最少资金是多少。

    题解

    观察每个节点的答案表达式

    dp[i]=min(dp[j]+(dis[i]-dis[j])*p[i]+q[i])

    一个斜率一个常数,显然需要斜率优化。

    先考虑树是一条链的情况

    设i到根节点的两个祖先为j和k,且deep[k]>deep[j]

    那么如果要保留j的话,要满足val(j)<val(k)

    dp[j]+dis[i]*p[i]-dis[j]*p[i]+q[i]<dp[k]+dis[i]*p[i]-dis[k]*p[i]+q[i]。

    dp[j]-dis[j]*p[i]<dp[k]-dis[k]*p[i]

    (dp[k]-dp[j])/(dis[k]-dis[j])>=p[i]

    这里dis是单调递增的,但p没有这种性质。

    所以我们可以开一个单调栈,然后每次查询在栈内二分。

    每次二份出来这个最优点就好了,插入一个数也一样,就是二分这个位置,把后面的都弹出。

    然后这条链变成了一棵树。

    可以先dfs这个树,然后对于一条链去维护这个栈。

    我们虽然在dfs树,但是在计算机内部实现是一个栈在反复压栈弹栈。

    所以我们需要支持维护这个栈,然后还需要支持撤销操作。

    实现的话就维护一个以deep为下标的线段树,区间l-r的节点代表的是deep在l-r的范围内的所有点组成的单调栈,因为同一时刻合法的点深度各不相同。

    所以我们要开O(n)单调栈,也可以提前开好,放在一个数组里。

    每次dfs到一个节点时,就在线段树上找到包含自己的所有区间,然后对这些区间进行“加入一个数”这个操作,同时记录被改掉的信息,方便撤销。

    对于一个查询,我们可以用树上倍增找出合法的深度区间,然后在线段树上查这个区间的最优解就好了,复杂度nlog2n

    细节:把撤销写挂了,调了好久。

    代码

    #include<iostream>
    #include<cstdio>
    #define N 200009 
    #define int ll
    using namespace std;
    typedef long long ll;
    ll n,q[N*30],dis[N],dp[N],qi[N],pi[N],lim[N];
    int tot,head[N],deep[N],remt[N][30],remv[N][30],p[N][30],now=1;
    int ddf;
    inline ll rd(){
        ll x=0;char c=getchar();bool f=0;
        while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
        while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
        return f?-x:x; 
    }
    struct SEG{int head,tail;}tr[N<<2];
    struct edge{int n,to,l;}e[N];
    inline void add(int u,int v,int l){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l;}
    inline double get_k(int j,int k){return (double)((double)dp[j]-dp[k])/((double)dis[j]-dis[k]);}
    inline int get_ans(int cnt,double p){
        int l=tr[cnt].head,r=tr[cnt].tail-1,ans=-1;    
        if(tr[cnt].head>=tr[cnt].tail)return q[tr[cnt].tail];
        while(l<=r){
            int mid=(l+r)>>1;
            if(get_k(q[mid],q[mid+1])>=p){ans=mid;r=mid-1;}else l=mid+1;
        }
        if(ans<0)ans=tr[cnt].tail;
        return q[ans];
    }
    int query(int cnt,int l,int r,int L,int R,double p){
        if(l>=L&&r<=R)return get_ans(cnt,p);
        int mid=(l+r)>>1;
        if(mid>=L&&mid<R){
            int x=query(cnt<<1,l,mid,L,R,p),y=query(cnt<<1|1,mid+1,r,L,R,p);
            if(get_k(x,y)>=p)return x;else return y;
        }
        else if(mid>=L)return query(cnt<<1,l,mid,L,R,p);
        else return query(cnt<<1|1,mid+1,r,L,R,p);
    }
    inline int findqueue(int cnt,int u){
        if(tr[cnt].head>=tr[cnt].tail)return tr[cnt].tail;
        int l=tr[cnt].head,r=tr[cnt].tail-1,ans=-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(get_k(q[mid],q[mid+1])>get_k(q[mid],u))ans=mid,r=mid-1;
            else l=mid+1;
        }
        if(ans<0)ans=tr[cnt].tail;
        return ans;
    }
    void build(int cnt,int l,int r){
        tr[cnt].tail=now-1;tr[cnt].head=now;
        now+=(r-l+1)+5;
        if(l==r)return;int mid=(l+r)>>1;
        build(cnt<<1,l,mid);build(cnt<<1|1,mid+1,r);
    }
    void insert(int cnt,int l,int r,int u,int x,int dep){
        int pos=findqueue(cnt,u)+1;
        remt[u][dep]=tr[cnt].tail;
        remv[u][dep]=q[pos];
        q[pos]=u;tr[cnt].tail=pos; 
        if(l==r)return;
        int mid=(l+r)>>1;
        if(mid>=x)insert(cnt<<1,l,mid,u,x,dep+1);
        else insert(cnt<<1|1,mid+1,r,u,x,dep+1);
    }
    void del(int cnt,int l,int r,int u,int x,int dep){    
        q[tr[cnt].tail]=remv[u][dep];
        tr[cnt].tail=remt[u][dep];
        if(l==r)return;
        int mid=(l+r)>>1;
        if(mid>=x)del(cnt<<1,l,mid,u,x,dep+1);
        else del(cnt<<1|1,mid+1,r,u,x,dep+1);
    }
    inline int jump(int x,int l){
        int now=x;
        for(int i=20;i>=0;--i)if(p[now][i]&&dis[x]-dis[p[now][i]]<=l)now=p[now][i];
        return now;
    }
    void dfs(int u){
        for(int i=1;(1<<i)<=deep[u];++i)p[u][i]=p[p[u][i-1]][i-1];
        if(u!=1){
            int x=jump(u,lim[u]);
            ddf=u;
            int y=query(1,1,n,deep[x],deep[p[u][0]],pi[u]);
            dp[u]=dp[y]+(dis[u]-dis[y])*pi[u]+qi[u];
        }
        insert(1,1,n,u,deep[u],0);
        for(int i=head[u];i;i=e[i].n){
           int v=e[i].to;dis[v]=dis[u]+e[i].l;
           deep[v]=deep[u]+1;p[v][0]=u;dfs(v); 
        }
        del(1,1,n,u,deep[u],0);
    } 
    signed main(){
        n=rd();int t=rd();int father;int l;
        for(int i=2;i<=n;++i){
            father=rd();l=rd();pi[i]=rd();qi[i]=rd();lim[i]=rd();
            add(father,i,l); 
        }
        build(1,1,n); 
        deep[1]=1;dfs(1);
        for(int i=2;i<=n;++i)printf("%lld
    ",dp[i]);
        return 0;
    } 
  • 相关阅读:
    超文本传输协议 HTTP/1.0 Hyptertext Transfer Protocol
    VB.NET中使用代表对方法异步调用
    蚂蚁解道德经(1)[转载]
    vb.net 类的属性的设置和获取问题
    VB.net入门(6):类~构造函数,事件
    什么是Ajax技术
    千里之外
    一个asp.net2005的页面文件调用CSS样式的BUG
    一个.net发送HTTP数据实体的类
    利用ASP发送和接收XML数据的处理方法
  • 原文地址:https://www.cnblogs.com/ZH-comld/p/10249199.html
Copyright © 2011-2022 走看看