zoukankan      html  css  js  c++  java
  • BZOJ3672/UOJ7 [Noi2014]购票

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

    本文作者:ljh2000
    作者博客:http://www.cnblogs.com/ljh2000-jump/
    转载请注明出处,侵权必究,保留最终解释权!

     

    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

    正解:CDQ分治+斜率优化DP+树分治

    解题报告:

      这道题是三算法合一经典好题...

      我们考虑如果是在一个序列上,就是simple的斜率优化裸题了。但是P[i]的变化不随i变化而规律性变化,所以我每次要求的最优斜率是不一样的,维护好下凸包,每次在凸包上二分找到最优值即可。

      对于树上的距离限制,我们肯定对于每个点要重建凸包,如果每次重建显然复杂度无法承受,所以我们需要改变节点的处理顺序,使得祖先上的点顺次加入并且不用重构,这很简单,只要考虑每个点能被最上面的哪些点更新到,排个序就可以扫一遍做完了。

      另外树分治是为了保证我整体上的处理复杂度是log次,CDQ分治的最大精髓就是先递归处理一部分,再用处理完的部分来更新未处理的部分,显然当祖先节点递归处理完之后,就可以用来处理子树,而每棵子树内部的点都共用了一条链上的祖先,更新答案,最后再递归处理就可以了。

      说的有点抽象,看看代码理解起来还是挺快的。其实是我懒,不想写公式... 

    //It is made by ljh2000
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    typedef long long LL;
    const int MAXN = 200011;
    const LL inf = (1LL<<62);
    int n,f[MAXN],ecnt,first[MAXN],to[MAXN],next[MAXN],size[MAXN],maxS[MAXN],cnt,dui[MAXN];
    LL p[MAXN],q[MAXN],lim[MAXN],dis[MAXN],w[MAXN],dp[MAXN];
    bool use[MAXN];
    struct node{ LL val; int id; }a[MAXN];
    inline bool cmp(node q,node qq){ return q.val>qq.val; }
    inline void link(int x,int y,LL z){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z; }
    inline LL upd(int i,int j){ return dp[j]+(dis[i]-dis[j])*p[i]+q[i]; }
    inline LL K(int x,int y){ return (dp[y]-dp[x])/(dis[y]-dis[x]); }
    inline int getint(){
        int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
        if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
    }
    
    inline LL getLL(){
        LL w=0; LL q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
        if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
    }
    
    inline void dfs(int x){//预处理每个节点的dis和size
    	size[x]=1;
    	for(int i=first[x];i;i=next[i]) {
    		dis[to[i]]=dis[x]+w[i];
    		dfs(to[i]); size[x]+=size[to[i]];
    	}
    }
    
    inline void find_root(int x,int S,int &rt){  
    	maxS[x]=0; size[x]=1;
    	for(int i=first[x];i;i=next[i]) {
    		int v=to[i]; if(use[v]) continue;
    		find_root(v,S,rt);
    		size[x]+=size[v]; maxS[x]=max(maxS[x],size[v]);
    	}
    	maxS[x]=max(maxS[x],S-size[x]);
    	if(maxS[x]<maxS[rt] && size[x]>1/*不然会GG!!!*/) rt=x;
    }
    
    inline void dfs2(int x){
    	a[++cnt].id=x; a[cnt].val=dis[x]-lim[x];
    	for(int i=first[x];i;i=next[i]) if(!use[to[i]]) dfs2(to[i]);
    }
    
    inline void solve(int x,int S){
    	if(S==1) return ; int rt=0,now;
    	find_root(x,S,rt);
    	for(int i=first[rt];i;i=next[i]) use[to[i]]=1;//把重心的儿子节点堵上
    	//CDQ分治
    	//先处理除了重心的子树之外的部分
    	solve(x,S-size[rt]+1);//重心也放入这个部分,方便以后讨论
    
    	cnt=0; for(int i=first[rt];i;i=next[i]) dfs2(to[i]);
    	sort(a+1,a+cnt+1,cmp);//把子树内所有点按能被更新到的最高高度自大往小排序
    	now=rt;//需要向外更新的是,重心到当前分治根节点的这条链上的所有点
    	int tail,l,r,mid,pos; tail=0;
    	for(int i=1;i<=cnt;i++) {
    		while(now!=f[x] && dis[a[i].id]-lim[a[i].id]<=dis[now]) {
    			while(tail>1 && K(dui[tail],now)>=K(dui[tail-1],dui[tail])) tail--;
    			//因为dis[now]递减,相当于是横坐标递减,可以看做是倒着加入,注意斜率判断方向
    			dui[++tail]=now; now=f[now];
    		}
    		if(tail>0) {
    			//在凸包上二分求得最优解
    			l=1; r=tail; pos=1;
    			while(l<=r) {
    				mid=(l+r)>>1; if(mid==tail) { pos=tail; break; }
    				if(K(dui[mid],dui[mid+1])>=p[a[i].id]) l=mid+1,pos=mid+1;//当前最优的应该是mid+1!!!
    				else r=mid-1;
    			}
    			dp[a[i].id]=min(dp[a[i].id] , upd(a[i].id,dui[pos]));
    		}
    	}
    	for(int i=first[rt];i;i=next[i]) solve(to[i],size[to[i]]);
    }
    
    inline void work(){
    	n=getint(); LL x; x=getint();
    	for(int i=2;i<=n;i++) {
    		f[i]=getint(); x=getLL(); link(f[i],i,x);
    		p[i]=getint(); q[i]=getLL(); lim[i]=getLL();
    	}
    	dfs(1); maxS[0]=n+1; for(int i=2;i<=n;i++) dp[i]=inf;
    	solve(1,size[1]);
    	for(int i=2;i<=n;i++) printf("%lld
    ",dp[i]);
    }
    
    int main()
    {
        work();
        return 0;
    }
    

      

  • 相关阅读:
    【Uvalive4960】 Sensor network (苗条树,进化版)
    【UVA 1151】 Buy or Build (有某些特别的东东的最小生成树)
    【UVA 1395】 Slim Span (苗条树)
    【UVA 10600】 ACM Contest and Blackout(最小生成树和次小生成树)
    【UVA 10369】 Arctic Network (最小生成树)
    【UVA 10816】 Travel in Desert (最小瓶颈树+最短路)
    【UVA 11183】 Teen Girl Squad (定根MDST)
    【UVA 11865】 Stream My Contest (二分+MDST最小树形图)
    【UVA 11354】 Bond (最小瓶颈生成树、树上倍增)
    【LA 5713 】 Qin Shi Huang's National Road System (MST)
  • 原文地址:https://www.cnblogs.com/ljh2000-jump/p/6404755.html
Copyright © 2011-2022 走看看