题目描述
oi国是一个温暖美丽的地方,其地图是一个树结构,树的根节点就是oi城的首都——orzboshi城。orzboshi城是国王boshi办公的地方,他每天都会收到来自其他城市的信息。oi国的信息传递的方式是这样的:
除了orzboshi城,每个城市都有一个信使,当i城产生一条信息的时候,信使i需要花
Si
的时间做准备,然后以
Vi
的速度向i城父节点跑去,到达下一座城市j的时候,信使可以选择继续跑,或者通知城j的信使j,让他继续送信。信使j要花
Sj
的时间做准备,然后以
Sj
的速度去送信。
boshi想要知道,所有小镇的信息最少要花多少时间才能送到orzboshi城呢?
输入描述
第一行:n,表示oi国有n座城市
接下来n-1行:x,y,z 表示x到y城之间有一条z米的道路
接下来n-1行:表示从2到n号城市的信使的准备时间S和速度V(跑1米要花V的时间)
输出描述
一行,n-1个数,表示2到n号城市的信息到orzboshi城的最短时间
数据范围
3≤N≤105 , 0≤Si≤109, , 1≤Vi≤109
题目分析
咋一看可能会觉得是一个有点水的dp,大概就是f[i]=min(f[j]+v[i]*(dis[i]-dis[j])+s[i]),dis[i]代表1到i的距离,然后j是i的祖先节点。
然后看向数据范围。。。肯定会TLE的吧啊喂!
虽然想到了斜率优化,但是这题没有决策单调性,用单调队列维护是行不通的。
于是看向英文的solution。。。决定和boshi大佬一起翻译一下(虽然大部分是boshi大佬的功劳。。。)那么说一说解法吧:
推斜率式
如果在考虑i的时候,dep[j]>dep[k],(dep:深度)并且选择j的决策更好,则有:
f[j]+v[i]∗(dis[i]−dis[j])+s[i]<f[j]+v[i]∗(dis[i]−dis[k])+s[i]
哇塞这个式子真好推。。。
注意,现在我们把 f[j]−f[k]dis[j]−dis[k] 记为g(j,k)
维护栈
要遍历这棵树就要dfs,可是。。。没有决策单调性(假如a的父节点是b,b的父节点是c,以此类推,则考虑b时选择c,在考虑a的时候选择b或者d都是有可能的,如果用单调队列维护的话,不能轻易弹出队首元素)
所以就维护栈好了。
总之,我们要维护斜率的单调性,单调递增,如图。
既然已经维护好单调性了,我们要找最优解。二分就可以找到队首(。。。栈首?)最优决策(怎么找接下来再讲),更新f[i],然后我们要把i放到栈里。可是。。。递归回去之后这个栈还是要用的,怎么能随便改来改去呢?
于是我们二分找到更新后栈里面i要放的位置(二分方法接下来讲),将栈顶指针修改为那个位置,并将那个位置替换为i。在dfs了i的所有子节点后,我们把栈顶指针移回去,并将那个位置原来的元素弄回来(用中间变量暂时储存即可),就可以回溯了。
二分怎么搞
1.二分找队首最优决策
int sfind(int l,int r,LL x){
int mid=0;
while(l<=r){
mid=(l+r)>>1;
if(g(q[mid+1],q[mid])<v[x])l=mid+1;
else if(g(q[mid],q[mid-1])>v[x])r=mid-1;
else return mid;
}
return mid;
}
想一想推斜率式的前提是
j>k
。
所以如果
g(q[mid+1],q[mid])<v[x]
,说明q[mid+1]比q[mid]更优。
如果
g(q[mid],q[mid−1])>v[x]
,说明q[mid-1]比q[mid]更优。
因此我们可以看作是寻找v[x]这条直线左移过程中可以碰到的第一个点。
2.二分更新后栈里面x要放的位置
int tfind(int l,int r,int x){//二分找x应插入到队尾的什么地方(mid后面)
int mid=0;
while(l<=r){
mid=(l+r)>>1;
if(g(q[mid+1],q[mid])<g(x,q[mid]))l=mid+1;
else if(g(q[mid],q[mid-1])>g(q[mid],x))r=mid-1;
else return mid;
}
return mid;
}
如果
g(q[mid+1],q[mid])<g(x,q[mid])
,如图,为了维护斜率单调递增,x应该要往右移
如果
g(q[mid],q[mid−1])>g(q[mid],x)
,如图,为了维护斜率单调递增,x应该向左移动
剩下的情况,如图,x插入到mid后面就可以了。(为什么这条线延长了?因为看向斜率式子:g(i,j)=g(j,i))
代码
#include<iostream>
#include<cstdio>
#include<climits>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
#define db double
LL read(){
LL q=0,w=1;char ch=' ';
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')q=q*10+(LL)(ch-'0'),ch=getchar();
return q*w;
}
const int N=100050;
int n,tot,top;
int h[N],to[N<<1],ne[N<<1];LL lo[N<<1];
LL s[N],v[N],f[N],dis[N];
int q[N];
void add(int x,int y,LL z){to[++tot]=y,ne[tot]=h[x],h[x]=tot,lo[tot]=z;}
db g(int j,int k){return (db)(f[j]*1.0-f[k]*1.0)/(db)(dis[j]*1.0-dis[k]*1.0);}
int sfind(int l,int r,LL x){//二分找队首最优决策
int mid=0;
while(l<=r){
mid=(l+r)>>1;
if(g(q[mid+1],q[mid])<v[x])l=mid+1;
else if(g(q[mid],q[mid-1])>v[x])r=mid-1;
else return mid;
}
return mid;
}
int tfind(int l,int r,int x){//二分找x应插入到队尾的什么地方(mid后面)
int mid=0;
while(l<=r){
mid=(l+r)>>1;
if(g(q[mid+1],q[mid])<g(x,q[mid]))l=mid+1;
else if(g(q[mid],q[mid-1])>g(q[mid],x))r=mid-1;
else return mid;
}
return mid;
}
void dfs(int x,int las){
int i,j,fg,kls,kltop;
fg=sfind(1,top,x);
f[x]=f[q[fg]]+(dis[x]-dis[q[fg]])*v[x]+s[x];//dp
fg=tfind(1,top,x);
kls=q[fg+1],kltop=top,top=fg+1,q[top]=x;//暂时储存被修改的top和q[fg+1]
for(i=h[x];i;i=ne[i]){
if(to[i]==las)continue;
dis[to[i]]=dis[x]+lo[i],dfs(to[i],x);
}
top=kltop,q[fg+1]=kls;//改回来
}
int main()
{
freopen("harbingers.in","r",stdin);
freopen("harbingers.out","w",stdout);
int i,x,y;LL z;
n=read();
for(i=1;i<n;i++)x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
for(i=2;i<=n;i++)s[i]=read(),v[i]=read();
dfs(1,-1);
for(i=2;i<=n;i++)printf("%lld ",f[i]);
return 0;
}