luogu P3994 高速公路

背景:

逃了物理竞赛,机房就 2 2 2人。

题目传送门:

https://www.luogu.org/problemnew/show/P3994

题意:

有一棵树,其中第 i i i个点到第 j j j个点的贡献为 d i s ( i , j ) ∗ P i + Q i dis(i,j)*P_i+Q_i dis(i,j)Pi+Qi.若 i i i j j j的祖先,则保证 P i ≤ P j P_i≤P_j PiPj

思路:

显然是一个在树上维护的斜率优化的 D P DP DP
对于一条根到叶子节点的路径的路径,它的斜率一定是单调的。那就好办了,因为它们就可以放在一起维护了。而对于不在同一棵子树内的儿子节点,回溯时注意单调队列内的点被删除的放回即可。

f i f_i fi表示 i i i点到 1 1 1号根节点的路径的最小值。
f i = ∑ j ∈ i . a n c e s t o r f j + P i ∗ d i s ( i , j ) + Q i f_i=\sum_{j∈i.ancestor}{f_j+P_i*dis(i,j)+Q_i} fi=ji.ancestorfj+Pidis(i,j)+Qi
由于 i , j i,j i,j在同一条链上,因此 d i s ( i , j ) = d i s ( i , 1 ) − d i s ( j − 1 ) dis(i,j)=dis(i,1)-dis(j-1) dis(i,j)=dis(i,1)dis(j1)
d i s i = d i s 1 , i dis_i=dis_{1,i} disi=dis1,i,则: f i = f j + ∑ j . a n c e s t o r P i ( d i s i − d i s j ) + Q i f_i=f_j+\sum_{j.ancestor}{P_i(dis_i-dis_j)+Q_i} fi=fj+j.ancestorPi(disidisj)+Qi
得到斜率方程: f j − f k d i s j − d i s k &lt; p i \frac{f_j-f_k}{dis_j-dis_k}&lt;p_i disjdiskfjfk<pi
套进去即可。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
	int n,m,len=0,head,tail;
	struct node{int x,y,z,next;} a[2000010];
	int last[1000010];
	LL p[1000010],q[1000010],dis[1000010],f[1000010];
	int que[1000010];
LL calc1(int x,int y)
{
	return dis[y]-dis[x];
}
LL calc2(int x,int y)
{
	return f[y]-f[x];
}
double calc(int x,int y)
{
	return (double)(calc2(x,y))/(double)(calc1(x,y));
}
void ins(int x,int y,int z)
{
	a[++len]=(node){x,y,z,last[x]}; last[x]=len;
}
int last_head[1000010],last_tail[1000010],tmp[1000010],pos[1000010];
void dfs(int x,int fa)
{
	last_head[x]=head,last_tail[x]=tail;
	while(head<tail&&calc(que[head],que[head+1])<=(double)p[x]) head++;
	f[x]=f[que[head]]+p[x]*calc1(que[head],x)+q[x];
	while(head<=tail&&calc(que[tail],x)<=calc(que[tail-1],que[tail])) tail--;
	tail++;
	tmp[x]=que[tail];
	pos[x]=tail;
	que[tail]=x;
	for(int i=last[x];i;i=a[i].next)
	{
		int y=a[i].y;
		if(y==fa) continue;
		dis[y]=dis[x]+a[i].z;
		dfs(y,x);
	}
	que[pos[x]]=tmp[x];
	head=last_head[x],tail=last_tail[x];
}
int main()
{
	int x,z;
	scanf("%d",&n);
	for(int i=2;i<=n;i++)
	{
		scanf("%d %d %lld %lld",&x,&z,&p[i],&q[i]);
		ins(i,x,z),ins(x,i,z);
	}
	head=1;
	tail=0;
	que[0]=0;
	dfs(1,0);
	for(int i=2;i<=n;i++)
		printf("%lld\n",f[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值