zoukankan      html  css  js  c++  java
  • NOI2014 购票

    Time Limit: 30 Sec Memory Limit: 512 MB

    Description

    今年夏天,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他们所花的最少资金是多少。

    Input

    第 1 行包含2个非负整数 n,t,分别表示城市的个数和数据类型(其意义将在后面提到)。输入文件的第 2 到 n 行,每行描述一个除SZ之外的城市。其中第 v 行包含 5 个非负整数 f_v,s_v,p_v,q_v,l_v,分别表示城市 v 的父亲城市,它到父亲城市道路的长度,票价的两个参数和距离限制。请注意:输入不包含编号为 1 的SZ市,第 2 行到第 n 行分别描述的是城市 2 到城市 n。

    Output

    输出包含 n-1 行,每行包含一个整数。其中第 v 行表示从城市 v+1 出发,到达SZ市最少的购票费用。同样请注意:输出不包含编号为 1 的SZ市。

    Sample Input

    7 3
    1 2 20 0 3
    1 5 10 100 5
    2 4 10 10 10
    2 9 1 100 10
    3 5 20 100 10
    4 4 20 0 10
    

    Sample Output

    40
    150
    70
    149
    300
    150 
    

    HINT
    图片
    对于所有测试数据,保证 0≤pv≤106,0≤qv≤1012,1≤fv<v;保证 0<sv≤lv≤2×1011,且任意城市到SZ市的总路程长度不超过 2×1011。
    输入的 t 表示数据类型,0≤t<4,其中:
    当 t=0 或 2 时,对输入的所有城市 v,都有 fv=v-1,即所有城市构成一个以SZ市为终点的链;
    当 t=0 或 1 时,对输入的所有城市 v,都有 lv=2×1011,即没有移动的距离限制,每个城市都能到达它的所有祖先;
    当 t=3 时,数据没有特殊性质。
    n=2×10^5

    简要题解

    首先,如果这道题不是在树上,而是在一条链上,那么这道题就是一个裸的有关i的信息不递增的斜率优化DP,只需要用单调队列维护一个下凸包,二分查找决策即可。

    [dp[i]=dp[j]+(dist[i]-dist[j])*p[i]+q[i]| ext{$j$是$i$的祖先且$dist[j]>=dist[i]-l[i]$} ]

    那么我们来考虑一下如何把这个dp搬到树上去做。
    考虑点分治,每一次我们找到重心以后,先递归处理一下原来的子树的根节点所在的新树的子树。然后,我们利用cdq分分治的思想,用从重心到根的链上的点来更新其它子树。
    把其它子树的每一个结点的(lim[i]=dist[i]-l[i])按从大到小排序,然后按顺序枚举这些点,一次把重心到到根的路径上的点从重心依次向根放入单调栈,满足单调栈里的元素全部可以转移,那么我们就可以按照斜率优化的一般套路来写了。

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    using namespace std;
    template<typename A>inline void read(A&a){a=0;char c=0;int f=1;while(c<'0'||c>'9')(c=getchar())=='-'?f=-1:0;while(c>='0'&&c<='9')a=(a<<3)+(a<<1)+c-'0',c=getchar();f==-1?a=-a:0;}
    char buf[30];template<typename A>inline void write(A a){if(a<0)putchar('-'),a=-a;int top=0;if(!a)buf[top=1]='0';while(a)buf[++top]=a%10+'0',a/=10;while(top)putchar(buf[top--]);}
    typedef long long ll;
    
    const int maxn=2e5+7;
    int n,x,sum,mima,rt;ll dp[maxn],dist[maxn],lim[maxn];
    int num[maxn],vis[maxn];
    
    struct Node{int fa;ll s,p,q,l;}a[maxn];
    struct Edge{int to,ne;ll w;}g[maxn<<1];int head[maxn],tot;
    inline void addedge(int x,int y,ll z){g[++tot].to=y;g[tot].w=z;g[tot].ne=head[x];head[x]=tot;}
    
    inline void Getdis(int x){
        for(register int i=head[x];i;i=g[i].ne){
            int y=g[i].to;
            dist[y]=dist[x]+g[i].w;lim[y]=dist[y]-a[y].l;Getdis(y);
        }
    }
    
    inline void Getroot(int x){
        num[x]=1;int f=0;
        for(register int i=head[x];i;i=g[i].ne){
            int y=g[i].to;if(vis[y])continue;
            Getroot(y);num[x]+=num[y];f=max(f,num[y]);
        }
        f=max(f,sum-num[x]);if(f<mima&&num[x]>1)mima=f,rt=x;//错误笔记:必须要有num[x]>1,不然会出现死循环
    }
    
    int p[maxn],cnt,q[maxn];
    inline void dfs(int x){
        p[++cnt]=x;
        for(register int i=head[x];i;i=g[i].ne)if(!vis[g[i].to])dfs(g[i].to);
    }
    
    inline ll Slope_y(int x,int y){return dp[x]-dp[y];}
    inline ll Slope_x(int x,int y){return dist[x]-dist[y];}
    inline void DP(int x,int rt){
        int now=rt,top=0;
        for(register int i=1;i<=cnt;++i){
            while(now!=a[x].fa&&lim[p[i]]<=dist[now]){
                while(top>1&&(long double)Slope_y(q[top-1],q[top])/Slope_x(q[top-1],q[top])<=(long double)Slope_y(q[top],now)/Slope_x(q[top],now))--top;//错误笔记:传入的参数是now而不是p[i] 
                q[++top]=now;now=a[now].fa;//错误笔记:上一行以及第53行中,要用long double实数形算,不然两个ll相乘可能会爆ll 
            }
            if(top){//错误笔记:if里面是只要单调队列/栈里面有元素就可以进行,而不一定要top>1,那个是加入元素的标准 
                int l=1,r=top;
                while(l<=r){//错误笔记:好像还是要加上=,因为取等号时,l+1也可以更新l ,但是r要赋为mid-1,不然会死循环! 
                    int mid=(l+r)>>1;if(mid==top){l=top;break;}
                    if((long double)Slope_y(q[mid],q[mid+1])/Slope_x(q[mid],q[mid+1])>=a[p[i]].p)l=mid+1;
                    else r=mid-1;
                }
                dp[p[i]]=min(dp[p[i]],dp[q[l]]+(dist[p[i]]-dist[q[l]])*a[p[i]].p+a[p[i]].q);
            }
        }
    }
    
    inline bool cmp(const int &x,const int &y){return lim[x]>lim[y];}//错误笔记:要把数组按照其lim值从大到小排序,方便DP里面的now指针从下往上扫 
    inline void Solve(int x,int size){
        if(size==1)return;sum=mima=size;Getroot(x);int RT=rt;
        for(register int i=head[RT];i;i=g[i].ne)vis[g[i].to]=1;
        Solve(x,size-num[RT]+1);
        cnt=0;for(register int i=head[RT];i;i=g[i].ne)dfs(g[i].to);
    	sort(p+1,p+cnt+1,cmp);DP(x,RT);
        for(register int i=head[RT];i;i=g[i].ne)Solve(g[i].to,num[g[i].to]);
    }
    
    int main(){
        read(n);read(x);memset(dp,0x7f,sizeof(dp));dp[1]=0;
        for(register int i=2;i<=n;++i){
            read(a[i].fa);read(a[i].s);read(a[i].p);read(a[i].q);read(a[i].l);
            addedge(a[i].fa,i,a[i].s);//addedge(i,a[i].fa,a[i].s);   错误笔记:不建双向边,不然会比较麻烦
        }
        Getdis(1);Solve(1,n);for(register int i=2;i<=n;++i)write(dp[i]),putchar('
    ');
    }
    
  • 相关阅读:
    从零开始学ios开发(十三):Table Views(下)Grouped and Indexed Sections
    求助三陀工作室
    2015.6-2017.6软件测试学习计划
    标签管理
    Git的分支管理(三)
    Git的分支管理(二)
    Git的分支管理(一)
    Git的远程仓库
    Git版本的管理
    Git的版本管理创建和修改
  • 原文地址:https://www.cnblogs.com/hankeke/p/noi2014-ticket.html
Copyright © 2011-2022 走看看