【题目】
原题地址
题目大意:一棵有根树,从一个城市 x x 到另一个城市 y y 的花费为 dis(x,y)∗px+qx d i s ( x , y ) ∗ p x + q x ,同时要求 dis(x,y)<=limx,y是x的祖先 d i s ( x , y ) <= l i m x , y 是 x 的 祖 先 ,求每个城市到1号城市的最小花费。
【题目分析】
这个花费柿子一看就是斜率优化的形式,树上斜率优化之前也写过(CF932F),不过那题没有距离限制,只需要在子树中。这里我们可以考虑一下利用树分治的复杂度证明来做。
【解题思路】
看到这题的形式就想起前几天做的树上斜率优化,但是这题往更新的时候是有距离限制的,不过我们先不管它。
先写个方程: f[x]=min(f[x],f[y]+(dis[x]−dis[y])∗p[x]+q[x]) f [ x ] = m i n ( f [ x ] , f [ y ] + ( d i s [ x ] − d i s [ y ] ) ∗ p [ x ] + q [ x ] ) ,同时 y y 是 x x 的祖先, dis[x]−dis[y]<=lim d i s [ x ] − d i s [ y ] <= l i m 。
整理一下得到 f[y]=dis[y]∗p[i]+f[x]−dis[x]∗p[x]−q[x] f [ y ] = d i s [ y ] ∗ p [ i ] + f [ x ] − d i s [ x ] ∗ p [ x ] − q [ x ] ,那么 f[x]−dis[x]∗p[x]−q[x] f [ x ] − d i s [ x ] ∗ p [ x ] − q [ x ] 表示过点 (dis[y],f[y]) ( d i s [ y ] , f [ y ] ) 的斜率为 p[x] p [ x ] 的直线的截距。
求最小值显然我们要维护的是一个下凸壳。
接下来因为距离的限制,如果暴力每次重构凸包显然不可行,但是不暴力我们似乎又无法得到凸包——我们需要一些奇技淫巧来做。
发现距离的限制是在一个范围内,可以考虑CDQ版的点分治。
找出分治重心后,我们先处理重心及其祖先的dp值,这样我们一会可以得到根到重心的凸包。
但是在构建凸包前,我们应该先考虑距离限制——可以将重心子树中所有点按能更新它的最深城市排序
枚举这些点的时候再从重心往上构建凸包即可。
总的时间复杂度是 O(nlog2n) O ( n l o g 2 n ) 的
在写的过程中遇到了挺多问题的,比如应该预处理什么,凸包上二分的界之类的,这里也写一些重点:
1.CDQ过程中先解决当前重心以上的部分,具体实现上可以先找出当前子树x重心,然后将所有与重心相连的点标记已访问,确保不会重复经过,再调用solve处理以 x x 为根的子树的上半部分,这一部分的 siz s i z 是 S−siz[root]+1 S − s i z [ r o o t ] + 1 ,其中S表示找重心时的总点数(+1是因为包括了x,方便处理)
2.处理当前重心的子树时要再dfs出 dis[x]−lim[x] d i s [ x ] − l i m [ x ] 的值,才能进行排序处理。
3.凸包上二分的时候还是最好加上边界判定,比如 mid==top m i d == t o p 的时候直接让 ret=top r e t = t o p 退出
4.CDQ还要记得别把后半部分忘记了。。。
【参考代码】
#include
using namespace std;
typedef long long LL;
const int N=2e5+10;
const LL INF=(1ll<<61);
int n,tot,cnt;
int fa[N],siz[N],head[N],mx[N],qs[N];
LL p[N],q[N],lim[N],dis[N],dp[N];
bool vis[N];
LL read()
{
LL ret=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c)){ret=ret*10+(c^48);c=getchar();}
return f?ret:-ret;
}
void write(LL x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10ll);
putchar(x%10^48);
}
struct Tway
{
int v,nex;
LL w;
};
Tway e[N];
void add(int u,int v,LL w)
{
e[++tot]=(Tway){v,head[u],w};
head[u]=tot;
}
struct Tnode
{
int id;
LL val;
};
Tnode a[N];
bool cmp(Tnode A,Tnode B)
{
return A.val>B.val;
}
void dfs(int x)
{
siz[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
dis[v]=dis[x]+e[i].w;
dfs(v);siz[x]+=siz[v];
}
}
void dfs2(int x)
{
a[++cnt].id=x;a[cnt].val=dis[x]-lim[x];
for(int i=head[x];i;i=e[i].nex)
if(!vis[e[i].v])
dfs2(e[i].v);
}
void getroot(int x,int S,int &root)
{
mx[x]=0;siz[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(vis[v])
continue;
getroot(v,S,root);
siz[x]+=siz[v];mx[x]=max(mx[x],siz[v]);
}
mx[x]=max(mx[x],S-siz[x]);
if(mx[x]x]>1)
root=x;
}
double slope(int x,int y)
{
return (double)(dp[y]-dp[x])/(double)(dis[y]-dis[x]);
}
LL calc(int x,int y)
{
return (LL)(dp[y]+(dis[x]-dis[y])*p[x]+q[x]);
}
void solve(int x,int S)
{
if(S==1)
return;
int root=0,now;
getroot(x,S,root);
for(int i=head[root];i;i=e[i].nex)
vis[e[i].v]=1;
solve(x,S-siz[root]+1);
cnt=0;
for(int i=head[root];i;i=e[i].nex)
dfs2(e[i].v);
sort(a+1,a+cnt+1,cmp);
now=root;
int l,r,mid,pos,top=0;
for(int i=1;i<=cnt;++i)
{
// for(int i=1;i<=top;++i)
// printf("%d ",qs[top]);
// puts("");
while(now!=fa[x] && dis[a[i].id]-lim[a[i].id]<=dis[now])
{
while(top>1 && slope(qs[top],now)>=slope(qs[top-1],qs[top]))
top--;
qs[++top]=now;now=fa[now];
}
if(top)
{
l=1,r=top,pos=1;
while(l<=r)
{
mid=(l+r)>>1;
if(mid==top)
{
pos=top;
break;
}
if(slope(qs[mid],qs[mid+1])1,pos=mid;
else
l=mid+1;
}
dp[a[i].id]=min(dp[a[i].id],calc(a[i].id,qs[pos]));
}
// printf("%d %d\n",top,pos);
}
for(int i=head[root];i;i=e[i].nex)
solve(e[i].v,siz[e[i].v]);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ3672.in","r",stdin);
freopen("BZOJ3672.out","w",stdout);
#endif
n=read();LL x=read();
for(int i=2;i<=n;++i)
{
fa[i]=read();x=read();add(fa[i],i,x);
p[i]=read();q[i]=read();lim[i]=read();
}
dfs(1);mx[0]=n+1;
for(int i=2;i<=n;++i)
dp[i]=INF;
solve(1,siz[1]);
for(int i=2;i<=n;++i)
write(dp[i]),puts("");
return 0;
}
【总结】
都写上面了。