题解 [Ceoi2009]harbingers

题意

给定一颗树,树中每个结点有一个邮递员,每个邮递员要沿着唯一的路径走向capital(1号结点),每到一个城市他可以有两种选择: 1.继续走到下个城市 2.让这个城市的邮递员替他出发。 每个邮递员出发需要一个准备时间W[I],他们的速度是V[I],表示走一公里需要多少分钟。 现在要你求出每个城市的邮递员到capital的最少时间(不一定是他自己到capital,可以是别人帮他)

思路

如果是一条链的话,预处理出路径的前缀和(D)

有以下DP转移方程

[f(i)=min(f(j)+W_i+V_i(D_i-D_j) ]

去掉min,转换一下

[f(j)=V_iD_j+f(i)-W_i-V_iD_i ]

这就可以斜率优化了,不过因为(V_i)并不单增,我们需要在单调栈中维护整个下凸壳,通过二分来找到最优决策点

接着放在树上,问题在于我们如何来维护单调栈。如果每一次还原被修改的元素,时间复杂度会达到(O(n^2))。实际上我们可以通过可持久化数组对单调栈的弹出魔改 来实现

这里讲第二种实际上是第一种懒得写对于每一次弹出的数,只挪指针,最后插入节点时将该插入位置原本的元素记录,回溯回来时还原即可

心得

自己的二分是真得差,都是看得别人的,其实就是将(mid)带入成要求的点即可

代码

#include <bits/stdc++.h>
#define N 220000
#define inf 998244353
using namespace std;
int n,tot,to[N],nex[N],head[N],st[N],top;
long long f[N],dis[N],s[N],val[N];
double t[N];
void add(int u,int v,long long w){
	to[++tot]=v;nex[tot]=head[u];
	head[u]=tot;val[tot]=w;
}
double k(int x,int y){
	if(dis[y]==dis[x]) return 0;
	double xx=(dis[y]-dis[x]),yy=(f[y]-f[x]);
	return yy/xx;
}
int query(int x){
	int L=1,R=top,mid;
	while(L<=R){
		mid=L+R>>1;
		if(k(st[mid],st[mid+1])<t[x]) L=mid+1;
		else if(k(st[mid-1],st[mid])>t[x]) R=mid-1;
		else return mid;
	}return mid;
}
int modify(int x){
	int L=1,R=top,mid;
	while(L<=R){
		mid=L+R>>1;
		if(k(st[mid],st[mid+1])<k(st[mid],x)) L=mid+1;
		else if(k(st[mid-1],st[mid])>k(st[mid-1],x)) R=mid-1;
		else return mid;
	}return mid;
}
void dfs(int u,int fa){
	int x=query(u),ttop,i;long long vval;
	f[u]=f[st[x]]+(dis[u]-dis[st[x]])*t[u]+s[u];
	x=modify(u);ttop=top;vval=st[x+1];st[top=x+1]=u;
	for(i=head[u];i;i=nex[i]){
		int v=to[i];if(v==fa) continue;
		dis[v]=dis[u]+val[i];dfs(v,u);
	}top=ttop;st[x+1]=vval;
}
int main(){
	int i,j;scanf("%d",&n);
	for(i=1;i<n;i++){
		int u,v;long long w;
		scanf("%d%d%lld",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}for(i=2;i<=n;i++) scanf("%lld%lf",&s[i],&t[i]);
	dfs(1,0);for(i=2;i<=n;i++) printf("%lld%s",f[i],i==n?"
":" ");
	return 0;
}
原文地址:https://www.cnblogs.com/fpjo/p/13961071.html