BZOJ3672 NOI2014 购票

传送门

题目大意,给定一棵有根树($1$号点是根),每个点有$5$个参数$fa,len,d,cst,unt$

$fa$表示点$x$的父节点编号,$d$表示$x$到父节点的距离。

在$x$可以花费$dis(x,y)\times unt_x+cst_x$的价格到达$x$的祖先$y$,其中$dis(x,y)$为$x$到$y$的距离,且必须满足$dis(x,y)\leq len_x$,求每个点到根的最小代价。

 

先考虑如果树是一条链怎么做,显然只是一个可以斜率优化的递推式。

$F_i$表示从$i$出发的最小代价,$dep_i$表示$i$到根的距离

$F_i=\min\{F_j+cst_i(dep_i-dep_j)+unt_i\}(dep_i-dep_j\leq len_i)$

$F_i-cst_i\times dep_i-unt_i+cst_i\times dep_j=F_j$

令$X_k=dep_k,Y_k=F_k$,那么问题就出转化为了求斜率为$cst_i$的过$(X_j,Y_j)$之一的直线使得与$y$轴截距最小,由于有一个$len$的范围,所以没有办法直接扫一遍维护下凸壳,我们只能采用$CDQ$分治,考虑前半部分的点对后半部分的贡献,将后半部分的点按照$dep_x-len_x$降序排序,前半部分的点按照$X_k$降序,每次动态由$X_k$从大到小的顺序插入满足$dep_k\geq dep_x-len_x$的$X_k,Y_k$维护下凸壳,再在凸壳上进行二分即可。

思考如何把$CDQ$分治转到树上,其实只需要每次找到树分治区域的重心,优先处理重心靠近根一侧的部分,在暴力更新一遍重心,然后将重心的儿子的子树的点拿出来按照$dep_x-len_x$降序排序,再把重心在分治区内的所有祖先依次拿出来维护凸壳用来更新,就完成了在树上的$CDQ$分治。

最终复杂度为$O(N\log^2N)$。

 

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 400020
#define INF 4000000000000000ll
using namespace std;
const int BufferSize=1<<19;
char buffer[BufferSize],*head,*tail;
char Getchar(){
    if(head==tail){
        int l=fread(buffer,1,BufferSize,stdin);
        tail=(head=buffer)+l;
    } return *head++;
}
LL read(){
	LL nm=0,fh=1; char cw=Getchar();
	for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
void write(LL x){if(x>9) write(x/10);putchar(x%10+'0');}
int n,m,sz[M],fs[M],to[M],nt[M],fa[M],tmp,root,mxs[M],top,S[M],sum,maxn,P[M];
LL F[M],dep[M],C[M],D[M],len[M],tot;
bool vis[M]; double Y(int i){return F[i]*1.0;} double X(int i){return dep[i]*1.0;}
bool conv(int t1,int t2,int t3){return (Y(t3)-Y(t2))*(X(t2)-X(t1))>(Y(t2)-Y(t1))*(X(t3)-X(t2));}
void link(int x,int y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;}
void init(int x){dep[x]+=dep[fa[x]];for(int i=fs[x];i!=-1;i=nt[i]) init(to[i]);}
bool cmp(int x,int y){return dep[x]-len[x]>dep[y]-len[y];}
LL getans(int i,int j){return F[j]+(dep[i]-dep[j])*C[i]+D[i];}
void fdrt(int x){
	sz[x]=1,mxs[x]=0;
	for(int i=fs[x];i!=-1;i=nt[i])
		if(!vis[to[i]]) fdrt(to[i]),mxs[x]=max(mxs[x],sz[to[i]]),sz[x]+=sz[to[i]];
	mxs[x]=max(mxs[x],sum-sz[x]); if(mxs[x]<maxn) maxn=mxs[x],root=x;
}
void ins(int x){while(tot>1&&!conv(x,P[tot],P[tot-1])) tot--; P[++tot]=x;}
void upd(int x){
	if(!tot) return;
	int md,mx=tot,mi=2,res=0; F[x]=min(F[x],getans(x,P[1]));
	while(mi<=mx){
		md=((mx+mi)>>1);
		if(getans(x,P[md])>getans(x,P[md-1])) mx=md-1;
		else mi=md+1,res=P[md];
	} if(res) F[x]=min(F[x],getans(x,res));
}
void dp(int x){S[++top]=x;for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) dp(to[i]);}
void solve(int anc,int x){
	vis[x]=true,tot=top=0;
	if(anc!=x) sum=sz[anc]-sz[x],maxn=M,fdrt(anc),solve(anc,root),tot=top=0;
	for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) dp(to[i]);
	for(int y=x;y!=anc;y=fa[y]){
		if(dep[x]-dep[fa[y]]>len[x]) break;
		F[x]=min(F[x],getans(x,fa[y]));
	}
	sort(S+1,S+top+1,cmp),tot=0;
	for(int i=1,now=x;i<=top;upd(S[i]),i++){
		while(now!=fa[anc]&&dep[now]>=dep[S[i]]-len[S[i]]) ins(now),now=fa[now];
	} tot=top=0;
	for(int i=fs[x];i!=-1;i=nt[i]){
		if(vis[to[i]]) continue; tot=top=0,maxn=M;
		sum=sz[to[i]],fdrt(to[i]),solve(to[i],root),tot=top=0;
	}
}
int main(){
	n=read(),read(),sum=n,memset(fs,-1,sizeof(fs));
	memset(F,0x3f,sizeof(F)),F[1]=0;
	for(int i=2;i<=n;i++){
		fa[i]=read(),dep[i]=read(),link(fa[i],i);
		C[i]=read(),D[i]=read(),len[i]=read();
	} init(1);
	maxn=M,fdrt(1),solve(1,root);
	for(int i=2;i<=n;i++) write(F[i]),putchar('\n');
	return 0;
}

 

  

 

posted @ 2018-10-06 11:24  OYJason  阅读(138)  评论(0编辑  收藏  举报