luogu P2305 [NOI2014] 购票
为什么树上问题一定要用点分治/fn
首先我们显然可以写出暴力的\(O(n^2)\)dp:设\(f_u\)表示\(u\)到\(1\)的最小费用,则枚举可转移点,有转移\(dp_u=dp_v+p_u(d_u-d_v)+q_u\)。
观察到这个式子很像斜率优化的形式,展开后发现与\(v\)相关的项是\(dp_v-d_vp_u\),我们可以看作在\((-d_v,dp_v)\)的点用一条斜率为\(-p_u\)的直线去截可以得到的最大截距。
容易想到用这些点构建下凸壳,但是这些转移点不是固定的因此难以一般性地构建。
考虑cdq分治。先按照深度排序,排完序之后左区间向右区间转移,跑出dfs序之后将其放到线段树上就可以解决只有子树内转移的问题,然后线段树上每个节点内部套一棵李超树维护直线。我们发现每个询问的是一段后缀区间,因此可以双指针,复杂度\(O(n\log^3n)\)看上去不太行。
发现按照深度排序后点的横坐标恰好递增,于是可以在线段树每个节点上构建凸壳,查询时因为询问斜率不单调因此需要二分,时间复杂度还是三个log但是看上去常数小了很多。
有没有更优秀的复杂度呢?答案是有的。我们考虑如果所有询问直线的区间都包含或都不包含左区间,则可以将右区间按照询问斜率排序,可以省去二分的log。同时,一条询问直线只会在一次查询种不完全包含左区间。因此将这两类直线分开转移即可。时间复杂度降至\(O(n\log ^2n)\)
code:
#include<bits/stdc++.h>
#define I inline
#define ll long long
#define db double
#define lb long db
#define N (200000+5)
#define M ((N<<2)+5)
#define K (1500+5)
#define mod 1000000007
#define Mod (mod-1)
#define eps (1e-5)
#define ull unsigned ll
#define it iterator
#define Gc() getchar()
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((k+1)*(x)+(y))
#define R(n) (1ll*rand()*rand()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using namespace std;vector<int> S[N];
int n,op,x,z,Bg[N],En[N],Bh;ll W[N],X[N],Y[N],Z[N],dp[N],Id[N];
I void Make(int x,int La){Bg[x]=++Bh;for(int i:S[x]) Make(i,x);En[x]=Bh;}
I bool C1(int x,int y){return W[x]<W[y];}I bool C2(int x,int y){return X[x]<X[y];}I bool C3(int x,int y){return W[x]-Z[x]>W[y]-Z[y];}
I db slope(int x,int y){return (dp[y]-dp[x])*1.0/(W[x]-W[y]);}
namespace Tree{
#define ls v<<1
#define rs v<<1|1
vector<int> S[M];int T[M];I void PF(int v,int x){while(T[v]>0&&slope(S[v][T[v]-1],S[v][T[v]])>slope(S[v][T[v]-1],x)) T[v]--;if(T[v]==S[v].size()-1) S[v].PB(0);S[v][++T[v]]=x;}
I void Ins(int x,int y,int z,int l=1,int r=n,int v=1){if(x<=l&&r<=y) return PF(v,z);int m=l+r>>1;x<=m&&(Ins(x,y,z,l,m,ls),0);y>m&&(Ins(x,y,z,m+1,r,rs),0);}
I void calc1(int v,int x){if(T[v]==-1) return;int l=-1,r=T[v],mid;while(l+1<r) mid=l+r>>1,(slope(S[v][mid],S[v][mid+1])<-X[x]?l:r)=mid;dp[x]=min(dp[x],dp[S[v][l+1]]+(W[x]-W[S[v][l+1]])*X[x]+Y[x]);}
I void calc2(int v,int x){if(T[v]==-1) return;while(T[v]>0&&slope(S[v][T[v]-1],S[v][T[v]])>-X[x]) T[v]--;dp[x]=min(dp[x],dp[S[v][T[v]]]+(W[x]-W[S[v][T[v]]])*X[x]+Y[x]);}
I void Q1(int x,int l=1,int r=n,int v=1){calc1(v,x);if(l==r)return;int m=l+r>>1;Bg[x]<=m?Q1(x,l,m,ls):Q1(x,m+1,r,rs);}
I void Q2(int x,int l=1,int r=n,int v=1){calc2(v,x);if(l==r)return;int m=l+r>>1;Bg[x]<=m?Q2(x,l,m,ls):Q2(x,m+1,r,rs);}
I void Cl(int x,int y,int l=1,int r=n,int v=1){T[v]=-1;S[v].clear();if(x<=l&&r<=y)return;int m=l+r>>1;x<=m&&(Cl(x,y,l,m,ls),0);y>m&&(Cl(x,y,m+1,r,rs),0);}
}
I void calc(int l=1,int r=n){
if(l==r) return;int m=l+r>>1,i,R=m;calc(l,m);sort(Id+l,Id+m+1,C1);sort(Id+m+1,Id+r+1,C3);
for(i=m+1;i<=r;i++) {if(W[Id[i]]-Z[Id[i]]>W[Id[m]]||W[Id[i]]-Z[Id[i]]<=W[Id[l]]) continue;while(R>=l&&W[Id[i]]-Z[Id[i]]<=W[Id[R]]) Tree::Ins(Bg[Id[R]],En[Id[R]],Id[R]),R--;Tree::Q1(Id[i]);}
sort(Id+m+1,Id+r+1,C2);while(R>=l) Tree::Ins(Bg[Id[R]],En[Id[R]],Id[R]),R--;for(i=m+1;i<=r;i++) W[Id[i]]-Z[Id[i]]<=W[Id[l]]&&(Tree::Q2(Id[i]),0);
for(i=l;i<=m;i++) Tree::Cl(Bg[Id[i]],En[Id[i]]);sort(Id+m+1,Id+r+1,C1);calc(m+1,r);
}
int main(){
freopen("1.in","r",stdin);freopen("1.out","w",stdout);
int i,j;scanf("%d%d",&n,&op);for(i=2;i<=n;i++) scanf("%d%lld%lld%lld%lld",&x,&W[i],&X[i],&Y[i],&Z[i]),W[i]+=W[x],S[x].PB(i);Make(1,0);
Me(Tree::T,-1);for(i=1;i<=n;i++) Id[i]=i;Me(dp,0x3f);dp[1]=0;sort(Id+1,Id+n+1,C1);calc(1,n); for(i=2;i<=n;i++) printf("%lld\n",dp[i]);
}