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]|\text{$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('\n');
}
posted @ 2018-08-19 15:50  hankeke303  阅读(563)  评论(0编辑  收藏  举报