【洛谷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;
}
原文地址:https://www.cnblogs.com/stoorz/p/15363573.html