【洛谷P2305】购票

题目

题目链接:https://www.luogu.com.cn/problem/P2305
今年夏天,NOI 在 SZ 市迎来了她三十周岁的生日。来自全国 \(n\) 个城市的 OIer 们都会从各地出发,到 SZ 市参加这次盛会。
全国的城市构成了一棵以 SZ 市为根的有根树,每个城市与它的父亲用道路连接。为了方便起见,我们将全国的 \(n\) 个城市用 \(1\sim 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 他们所花的最少资金是多少。
\(n\leq 2\times 10^5\)\(0\leq p_i\leq 10^6\)

思路

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

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

其中 \(y\)\(x\) 的祖先并且 \(dis_x-dis_y\leq l_x\)
这个东西看上去可以斜率优化,但是因为有祖先以及距离的限制并不是很好直接搞。
先 dfs 一次求出所有点的出栈顺序,然后再 dfs 一次按照 dfs 序考虑转移。对于一个点 \(x\),可以通过倍增求出他深度最小的且满足 \(dis_x-dis_y\leq l_x\) 的祖先 \(y\),那么此时在出栈顺序的序列中,\(x\)\(y\) 之间的所有元素,要么是 \(x\)\(y\) 链上的点,要么是第二次 dfs 还没有访问过的点。那么等于需要支持区间询问当斜率为 \(k\) 时,区间内点的最小截距。
因为斜率 \(p_i\leq 10^6\),用线段树套动态开点李超树就可以了。
时间复杂度 \(O(n\log^2 n)\),空间复杂度 \(O(n\log 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]<<"\n";
	return 0;
}
posted @ 2021-10-03 09:58  stoorz  阅读(26)  评论(0编辑  收藏  举报