• 【洛谷P2305】购票


    题目

    题目链接:https://www.luogu.com.cn/problem/P2305
    今年夏天,NOI 在 SZ 市迎来了她三十周岁的生日。来自全国 (n) 个城市的 OIer 们都会从各地出发,到 SZ 市参加这次盛会。
    全国的城市构成了一棵以 SZ 市为根的有根树,每个城市与它的父亲用道路连接。为了方便起见,我们将全国的 (n) 个城市用 (1sim n) 的整数编号。其中 SZ 市的编号为 (1)。对于除 SZ 市之外的任意一个城市 (v),我们给出了它在这棵树上的父亲城市 (f_v) 以及到父亲城市道路的长度 (s_v)
    从城市 (v) 前往 SZ 市的方法为:选择城市 (v) 的一个祖先 (a),支付购票的费用,乘坐交通工具到达 (a)。再选择城市 (a) 的一个祖先 (b),支付费用并到达 (b)。以此类推,直至到达 SZ 市。
    对于任意一个城市 (v),我们会给出一个交通工具的距离限制 (l_v)。对于城市 (v) 的祖先 A,只有当它们之间所有道路的总长度不超过 (l_v) 时,从城市 (v) 才可以通过一次购票到达城市 A,否则不能通过一次购票到达。
    对于每个城市 (v),我们还会给出两个非负整数 (p_v,q_v) 作为票价参数。若城市 (v) 到城市 A 所有道路的总长度为 (d),那么从城市 (v) 到城市 A 购买的票价为 (dp_v+q_v)
    每个城市的 OIer 都希望自己到达 SZ 市时,用于购票的总资金最少。你的任务就是,告诉每个城市的 OIer 他们所花的最少资金是多少。
    (nleq 2 imes 10^5)(0leq p_ileq 10^6)

    思路

    很显然有一个 dp:设 (f[x]) 表示到达 (x) 的最小代价。转移

    [f[x]=min(f[y]+(dis_x-dis_y)p_x+q_x) ]

    其中 (y)(x) 的祖先并且 (dis_x-dis_yleq l_x)
    这个东西看上去可以斜率优化,但是因为有祖先以及距离的限制并不是很好直接搞。
    先 dfs 一次求出所有点的出栈顺序,然后再 dfs 一次按照 dfs 序考虑转移。对于一个点 (x),可以通过倍增求出他深度最小的且满足 (dis_x-dis_yleq l_x) 的祖先 (y),那么此时在出栈顺序的序列中,(x)(y) 之间的所有元素,要么是 (x)(y) 链上的点,要么是第二次 dfs 还没有访问过的点。那么等于需要支持区间询问当斜率为 (k) 时,区间内点的最小截距。
    因为斜率 (p_ileq 10^6),用线段树套动态开点李超树就可以了。
    时间复杂度 (O(nlog^2 n)),空间复杂度 (O(nlog n))。因为动态开点李超树一次修改只会增加 (O(1)) 个节点。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int N=200010,M=1000010,LG=19;
    int n,tot,typ,head[N],id[N],fa[N][LG+1];
    ll p[N],q[N],l[N],s[N],f[N];
    
    struct edge
    {
    	int next,to;
    }e[N*2];
    
    void add(int from,int to)
    {
    	e[++tot]=(edge){head[from],to};
    	head[from]=tot;
    }
    
    void dfs1(int x)
    {
    	for (int i=1;i<=LG;i++)
    		fa[x][i]=fa[fa[x][i-1]][i-1];
    	for (int i=head[x];~i;i=e[i].next)
    		s[e[i].to]+=s[x],dfs1(e[i].to);
    	id[x]=++tot;
    }
    
    int jump(int x,ll d)
    {
    	for (int i=LG;i>=0;i--)
    		if (s[x]-s[fa[x][i]]<=d)
    			d-=s[x]-s[fa[x][i]],x=fa[x][i];
    	return x;
    }
    
    ll calc(int x,ll k)
    {
    	return -s[x]*k+f[x];
    }
    
    struct SegTree2
    {
    	int tot,lc[N<<5],rc[N<<5],res[N<<5];
    	
    	int update(int x,int l,int r,int v)
    	{
    		if (!x) { res[++tot]=v; return tot; }
    		if (l==r)
    		{
    			if (calc(v,l)<calc(res[x],l)) res[x]=v;
    			return x;
    		}
    		int mid=(l+r)>>1;
    		if (calc(v,l)<calc(res[x],l))
    		{
    			if (calc(v,mid)<calc(res[x],mid))
    				rc[x]=update(rc[x],mid+1,r,res[x]),res[x]=v;
    			else
    				lc[x]=update(lc[x],l,mid,v);
    		}
    		else
    		{
    			if (calc(v,mid)<calc(res[x],mid))
    				lc[x]=update(lc[x],l,mid,res[x]),res[x]=v;
    			else
    				rc[x]=update(rc[x],mid+1,r,v);
    		}
    		return x;
    	}
    	
    	ll query(int x,int l,int r,int k)
    	{
    		if (!x) return 1e18;
    		if (l==r) return calc(res[x],k);
    		int mid=(l+r)>>1;
    		if (k<=mid) return min(query(lc[x],l,mid,k),calc(res[x],k));
    			else return min(query(rc[x],mid+1,r,k),calc(res[x],k));
    	}
    }seg2;
    
    struct SegTree1
    {
    	int rt[N*4];
    	
    	void update(int x,int l,int r,int k,int v)
    	{
    		rt[x]=seg2.update(rt[x],0,1e6,v);
    		if (l==r) return;
    		int mid=(l+r)>>1;
    		if (k<=mid) update(x*2,l,mid,k,v);
    			else update(x*2+1,mid+1,r,k,v);
    	}
    	
    	ll query(int x,int l,int r,int ql,int qr,ll v)
    	{
    		if (ql<=l && qr>=r) return seg2.query(rt[x],0,1e6,v);
    		int mid=(l+r)>>1; ll res=1e18;
    		if (ql<=mid) res=min(res,query(x*2,l,mid,ql,qr,v));
    		if (qr>mid) res=min(res,query(x*2+1,mid+1,r,ql,qr,v));
    		return res;
    	}
    }seg1;
    
    void dfs2(int x)
    {
    	int y=jump(x,l[x]);
    	if (x>1)
    		f[x]=seg1.query(1,1,n,id[x],id[y],p[x])+s[x]*p[x]+q[x];
    	seg1.update(1,1,n,id[x],x);
    	for (int i=head[x];~i;i=e[i].next)
    		dfs2(e[i].to);
    }
    
    int main()
    {
    	memset(head,-1,sizeof(head));
    	scanf("%d%d",&n,&typ);
    	for (int i=2,x;i<=n;i++)
    	{
    		scanf("%d%lld%lld%lld%lld",&x,&s[i],&p[i],&q[i],&l[i]);
    		add(x,i); fa[i][0]=x;
    	}
    	tot=0; s[0]=-1e18;
    	dfs1(1); dfs2(1);
    	for (int i=2;i<=n;i++)
    		cout<<f[i]<<"
    ";
    	return 0;
    }
    
  • 相关阅读:
    单调队列
    2019牛客暑期多校训练营(第一场)
    没有上司的舞会
    飞碟解除器
    最小费用最大流
    prim
    merge_sort
    CCF认证之——相反数
    CCF考试认证模拟练习——数字排序
    算法之分治法
  • 原文地址:https://www.cnblogs.com/stoorz/p/15363573.html
Copyright © 2020-2023  润新知