• 【BZOJ3672】[Noi2014]购票 树分治+斜率优化


    【BZOJ3672】[Noi2014]购票

    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

    题解:做这种题就怕突然灵光一现,然后自己yy了一发,到对拍1w组的时候才发现自己的做法完全GG,于是只好重写。。。

    先分享一下做这道题的思路(前置技能:BZOJ1492货币兑换),如果着急和MM约会,可以直接看最后两段。

    ——————————————从此处开始略过——————————————

    蒟蒻的独白:“不就是将cdq分治换成树分治嘛,有什么难的?这式子也太水了:用dep[i]表示根到i的距离,f[i]表示从i到根的最小购票费用,显然 $f[i]=min(f[j]+(dep[i]-dep[j])*p[i]+q[i]) ightarrow f[j]=dep[j]*p[i]+f[i]-q[i]-dep[i]*p[i]$ 式子都推出来了,搞呗!当我们以x为分治中心时,可选的i就是x子树中的所有点,可选的j就是根到i路径上的所有点(当然要满足i,j的距离<=li啦~),然后我就先递归分治x的父亲的那部分(在有根树中好像这个不能叫子树吧?),然后将x到根路径上的所有的点都拎出来维护个凸包,枚举x子树中的所有点,然后每个点都在那个凸包上二分一下,就完事啦!WA。 “突然发现,我们在搞凸包的时候,会丢掉很多点,新加入一些深度更小的点,但是由于有距离限制,这些深度更小的点不能更新x子树中的一些点,于是,这就要求我们将x子树中的点也都拎出来,一起搞。 “思路来了,我们将x到根路径上的所有点都用数组A存起来,x子树中的点都用数组B存起来,并按照(dep-l)排好序,那么我们同时扫这两个数组,边维护凸包边二分更新答案,感觉很棒!WA。 “惊讶的发现!x居然是降序的!无可奈何,重新搞。WA。 “1w组对拍都过了?woc用叉积会爆long long!无可奈何,用double。AC。”

    ————————————————省略结束————————————————

    总结一下全过程(翻译一下代码):

    1.当我们以x为分治中心时,先从网上一直延伸直到第一个已经访问过的节点,然后将这个最高点记录下来(一会才用!),然后分治处理x的父亲那部分
    2.处理完父亲后,我们将从x到那个最高点的路径上的点都拎出来扔到数组A中,再DFSx的子树中的点,放到B中,并按dep-l从大到小排序。
    3.同时扫A和B,进行斜率优化
    4.分治处理x的子树

    本人的代码不可读性还是比较高的,WA的同学请注意:在我们拎出x子树中的点时,就算访问到之前被标记过(做过分治中心)的点时也要将它放入数组中更新,只是不继续延伸下去。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <cmath>
    #include <algorithm>
    #define x(_) (dep[_])
    #define y(_) (f[_])
    using namespace std;
    const int maxn=200010;
    typedef long long ll;
    int n,m,cnt,maxx,tot,root;
    int fa[maxn],siz[maxn],to[maxn],next[maxn],head[maxn],A[maxn],st[maxn],vis[maxn],B[maxn];
    ll val[maxn],dep[maxn],len[maxn],p[maxn],q[maxn],lim[maxn],f[maxn];
    ll rd()
    {
    	ll ret=0;	char gc=getchar();
    	while(gc<'0'||gc>'9')	gc=getchar();
    	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
    	return ret;
    }
    bool cmp(int a,int b)
    {
    	return dep[a]-lim[a]>dep[b]-lim[b];
    }
    void add(int a,int b)
    {
    	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
    }
    void getr(int x,int fa)
    {
    	siz[x]=1;
    	int i,mx=0;
    	for(int i=head[x];i!=-1;i=next[i])
    	{
    		if(vis[to[i]]||to[i]==fa)	continue;
    		getr(to[i],x),siz[x]+=siz[to[i]],mx=max(mx,siz[to[i]]);
    	}
    	if(maxx>max(mx,tot-siz[x]))	maxx=max(mx,tot-siz[x]),root=x;
    }
    void getB(int x)
    {
    	B[++B[0]]=x;
    	if(vis[x])	return ;
    	for(int i=head[x];i!=-1;i=next[i])	getB(to[i]);
    }
    double getk(int a,int b)
    {
    	if(x(a)==x(b))	return 1e15*(y(a)>y(b)?1.0:-1.0);
    	else	return (double)(y(a)-y(b))/(x(a)-x(b));
    }
    void calc(int x)
    {
    	if(!m)	return ;
    	int mid,l=1,r=m;
    	double k=p[x];
    	while(l<r)
    	{
    		mid=l+r>>1;
    		if(getk(A[mid],A[mid+1])>k)	l=mid+1;
    		else	r=mid;
    	}
    	f[x]=min(f[x],f[A[l]]+(dep[x]-dep[A[l]])*p[x]+q[x]);
    }
    void dfs(int x)
    {
    	vis[x]=1;
    	int i,j,top=x;
    	while(top!=1&&!vis[fa[top]])	top=fa[top];
    	if(x!=top)	maxx=1<<30,tot=siz[top]-siz[x],getr(top,0),dfs(root);
    	if(top!=1)	top=fa[top];
    	st[st[0]=1]=x;
    	while(st[st[0]]!=top)	st[st[0]+1]=fa[st[st[0]]],st[0]++;
    	for(B[0]=0,i=head[x];i!=-1;i=next[i])	getB(to[i]);
    	sort(B+1,B+B[0]+1,cmp);
    	for(m=0,i=1,j=1;i<=st[0];i++)
    	{
    		for(;j<=B[0]&&dep[st[i]]<dep[B[j]]-lim[B[j]];j++)	calc(B[j]);
    		while(m>1&&getk(A[m-1],A[m])<=getk(A[m],st[i]))	m--;
    		A[++m]=st[i];
    	}
    	for(;j<=B[0];j++)	calc(B[j]);
    	for(i=head[x];i!=-1;i=next[i])	if(!vis[to[i]])	maxx=1<<30,tot=siz[to[i]],getr(to[i],x),dfs(root);
    }
    int main()
    {
    	n=rd(),rd();
    	int i;
    	memset(head,-1,sizeof(head));
    	for(i=2;i<=n;i++)
    	{
    		fa[i]=rd(),len[i]=rd(),p[i]=rd(),q[i]=rd(),lim[i]=rd();
    		add(fa[i],i);
    		dep[i]=dep[fa[i]]+len[i];
    	}
    	memset(f,0x3f,sizeof(f)),f[1]=0;
    	maxx=1<<30,tot=n,getr(1,0),dfs(root);
    	for(i=2;i<=n;i++)	printf("%lld
    ",f[i]);
    	return 0;
    }

     

  • 相关阅读:
    分布式事务的MQ实现
    zipkin 介绍入门
    线程5问?
    微服务分布式系统架构,转载,备份
    微服务,分布式架构
    史上最全 40 道 Dubbo 面试题及答案,看完碾压面试官!
    Tomcat优化
    windows 下安装kafka
    经典台词
    分布式锁3种实现
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7072415.html
Copyright © 2020-2023  润新知