题解[P2305 购票]
题意:
给定一棵树,每个点到其父亲距离为 \(s_i\)。
每个点能从到此点距离不超过 \(l_i\) 的祖先跳过来。
代价是 \(d\times p_i+q_i\),其中 \(d\) 为到祖先的距离。
求每个点从根节点跳过来的最小代价 \(f_i\)。
\(n\leq 2\times 10^5\)
以下说明中 \(u_x=p_x,v_x=q_x\),\(dis_x\) 为 \(x\) 到根的距离和。
-
考虑每个 \(y\) 对 \(x\) 的贡献,则有 \(dfn_y+1\leq dfn_x\leq dfn_y+sz_y-1\),自然想到线段树分治。
-
按照 \(dfs\) 序处理每一个点,用线段树将上面的区间限制拆成几个区间,将这些区间内加上点 \((\operatorname{X}_y,\operatorname{Y}_y)\) 。
-
这可以在处理到线段树上的叶子结点时,保证了其 \(f\) 值已求出,可对其子树区间动态加入 \((\operatorname{X}_y,\operatorname{Y}_y)\)。
-
中序遍历整颗线段树,将区间被加上的点按 \(\operatorname{X}\) 排序后,维护一个下凸包,对区间内的每个点找出最优决策点。
但同时有 \(\operatorname{X}_y\leq tl_x\) 的限制。
- 那就应该对区间内的 \(tl_x\) 排序,依次加进满足条件的 \(\operatorname{X}_y\),再对每个 \(\operatorname{K}_x\) 在凸包上二分得到最优决策点。
而每个点在线段树上会被其祖先更新 \(O(\log n)\) 次,每次二分也是 \(O(\log n)\),总时间是 \(O(n\log^2 n)\)。
-
由于排序只基于 \(\operatorname{X},\operatorname{K},tl\),在预处理完之后成为定值,在处理之前先按 \(\operatorname{X}_y,\operatorname{K}_x\) 排序,加入线段树上对应节点。
-
一个点被贡献的一定是其父节点,题目数据范围能保证其 \(dis\) 互不相同,不必担心凸包的 \(\operatorname{Y}\) 坐标。
代码:(卡精度,要开 \(\text{long double}\))
#include<bits/stdc++.h>
#define ll long long
#define db long double
using namespace std;
const ll inf=4e18;
const int N=2e5+10,M=5e6+10;
int n,m,x,y,tt,xx,yy,l_,r_,tot,pl,pr,pmid,pans;char ch;
int to[N],nextn[N],h[N],edg,dfn[N],sz[N],rev[N];
inline void add(int x,int y){to[++edg]=y,nextn[edg]=h[x],h[x]=edg;}
ll u[N],v[N],dis[N],tl[N],l[N],w[N],f[N];
inline void read(int &x){
x=0;ch=getchar();while(ch<47)ch=getchar();
while(ch>47)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}
inline void read(ll &x){
x=0;ch=getchar();while(ch<47)ch=getchar();
while(ch>47)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}
void write(ll x){if(x>9)write(x/10);putchar(48+x%10);}
struct node{
int i;ll x,tl;node()=default;
node(int _i,ll _x,ll _tl):i(_i),x(_x),tl(_tl){}
}a[N];
bool cmpx(node &a,node &b){return a.x>b.x;}
bool cmptl(node &a,node &b){return a.tl>b.tl;}
void init(int x,int anc){
int i,y;rev[dfn[x]=++tt]=x;dis[x]+=dis[anc];
tl[x]=l[x]-dis[x];w[x]=dis[x]*u[x]+v[x];sz[x]=1;
for(i=h[x];y=to[i];i=nextn[i])init(y,x),sz[x]+=sz[y];
}
int toq[M],nextnq[M],hq[N<<2],edgq;
int tos[M],nextns[M],hs[N<<2],edgs;
int tor[M],nextnr[M],hr[N<<2],edgr;
inline void addq(int x,int y){toq[++edgq]=y,nextnq[edgq]=hq[x],hq[x]=edgq;}
inline void addr(int x,int y){tor[++edgr]=y,nextnr[edgr]=hr[x],hr[x]=edgr;}
#define ls k<<1
#define rs k<<1|1
void updateq(int k,int l,int r){
if(xx<=l&&r<=yy)addq(k,x);
else {
int mid=(l+r)>>1;
if(xx<=mid)updateq(ls,l,mid);
if(mid<yy)updateq(rs,mid+1,r);
}
}
void updater(int k,int l,int r,int pos){
addr(k,x);
if(l^r){
int mid=(l+r)>>1;
if(pos<=mid)updater(ls,l,mid,pos);
else updater(rs,mid+1,r,pos);
}
}
struct point{
db x,y;point()=default;point(db _x,db _y):x(_x),y(_y){}
inline bool operator <=(const point &a)const {return y*a.x<=x*a.y;}
point operator -(const point &a)const {return point(x-a.x,y-a.y);}
}p[N],t[N],tmp;
bool bb[N];
bool dzt;
void solve(int k,int l,int r){
int i,y;
if(hr[k]){
dzt=(l==6)&&(r==9);
r_=tot=0;
for(i=hq[k];y=toq[i];i=nextnq[i]){
tmp=point(-dis[y],f[y]);t[++tot]=tmp;
while(r_>1&&(tmp-p[r_])<=(p[r_]-p[r_-1]))--r_;
p[++r_]=tmp;
}
int j=0;
r_=0;
for(i=hr[k];y=tor[i];i=nextnr[i]){
while(t[j+1].x<=tl[y]&&j<tot){
tmp=t[++j];
while(r_>1&&(tmp-p[r_])<=(p[r_]-p[r_-1]))--r_;
p[++r_]=tmp;
}
tmp=point(1,-u[y]);
if(r_>1){
pl=2,pr=r_;pans=0;
while(pl<=pr){
pmid=(pl+pr)>>1;
if((p[pmid]-p[pmid-1])<=tmp)pans=pmid,pl=pmid+1;
else pr=pmid-1;
}
if(!pans)pans=1;
}
else pans=r_;
if(pans)f[y]=min(f[y],(ll)p[pans].y+u[y]*(ll)p[pans].x);
}
}
if(l^r){
int mid=(l+r)>>1;
solve(ls,l,mid);
solve(rs,mid+1,r);
}
else y=rev[l],f[y]+=w[y];
}
main(){
read(n);read(x);register int i;
for(i=2;i<=n;++i){
read(x);add(x,i);f[i]=inf;
read(dis[i]);read(u[i]);
read(v[i]);read(l[i]);
}
init(1,0);
for(i=1;i<=n;++i)a[i]=node(i,-dis[i],tl[i]);
sort(a+1,a+n+1,cmpx);
for(i=1;x=a[i].i,i<=n;++i)if(sz[x]^1){
xx=dfn[x]+1,yy=dfn[x]+sz[x]-1;
updateq(1,1,n);
}
sort(a+1,a+n+1,cmptl);
for(i=1;x=a[i].i,i<=n;++i)updater(1,1,n,dfn[x]);
solve(1,1,n);
for(i=2;i<=n;++i)write(f[i]),putchar('\n');
}