• [NOI2014]购票


    题目

    读懂题目之后能写出一个dp方程,(dp_i=dp_j+(d_i-d_j)p_i+q_i(d_i-d_jleq lim_i)),其中(d_i)是根路径前缀和

    不难发现这个东西长得像斜率优化,需要建个凸壳来搞一搞;不难想到一个树剖+线段树维护的无脑做法,是(O(nlog^3n))的,看起来和暴力差不多;

    考虑有脑做法————有根树点分治,假设我们当前在处理一棵以(x)为根的有根树,流程大概长这个样子

    • 找到重心(nw)

    • 将重心与其儿子断开,递归处理重心所在联通块(当然,根也在这个联通块中)

    • 考虑(nw)(x)路径上的点对(nw)子树内部点产生的影响

    • 递归处理(nw)的子树

    在这道题中,我们将重心子树中的点都搞出来,按照(lim_i -dis_i)从小到大排序,(dis_i)是点(i)到根的距离;之后把重心到当前根上的点拿出来,按照距离排序;开个指针扫,把符合条件的点加入下凸壳即可,复查询的时候直接二分,复杂度是(O(nlog^2n))

    代码

    #include<bits/stdc++.h>
    #define re register
    #define LL long long
    #define max(a,b) ((a)>(b)?(a):(b))
    #define min(a,b) ((a)<(b)?(a):(b))
    inline LL read() {
    	char c=getchar();LL x=0;while(c<'0'||c>'9')c=getchar();
    	while(c>='0'&&c<='9')x=10ll*x+c-48,c=getchar();return x;
    }
    const double eps=1e-8;const int maxn=2e5+5;
    struct E{int v,nxt;}e[maxn];
    int head[maxn],fa[maxn],S,mx[maxn],rt,vis[maxn],n;
    int lp,top,st[maxn],cnt,bl[maxn],num,nw,sk[maxn],sum[maxn];
    LL p[maxn],q[maxn],dis[maxn],lm[maxn],w[maxn],dp[maxn];
    inline void add(int x,int y) {
    	e[++num].v=y;e[num].nxt=head[x];head[x]=num;
    }
    void getrt(int x) {
    	sum[x]=1;mx[x]=0;
    	for(re int i=head[x];i;i=e[i].nxt) {
    		if(vis[i])continue;getrt(e[i].v);
    		sum[x]+=sum[e[i].v];mx[x]=max(mx[x],sum[e[i].v]);
    	}
    	mx[x]=max(mx[x],S-sum[x]);if(mx[x]<mx[rt])rt=x;
    }
    void getdis(int x) {
    	st[++top]=x;
    	for(re int i=head[x];i;i=e[i].nxt) 
    		if(!vis[i])dis[e[i].v]=dis[x]+w[e[i].v],getdis(e[i].v);
    }
    inline int cop(int A,int B) {return dis[A]<dis[B];}
    inline int cmp(int A,int B) {return lm[A]-dis[A]<lm[B]-dis[B];}
    inline int dcmp(double a,double b){return a+eps>b&&a-eps<b;}
    inline double slope(int a,int b) {
    	return (double)(dp[b]-dp[a])/(double)(dis[b]-dis[a]);
    }
    inline void ins(int x) {
    	if(nw<1) {sk[++nw]=x;return;}
    	while(nw>1&&slope(sk[nw-1],x)<slope(sk[nw-1],sk[nw]))--nw;
    	sk[++nw]=x;
    }
    inline int fid(LL k) {
    	if(nw<=1)return sk[nw];
    	if(slope(sk[nw-1],sk[nw])<k) return sk[nw];
    	int l=1,r=nw-1,h=1;
    	while(l<=r) {
    		int mid=l+r>>1;double K=slope(sk[mid],sk[mid+1]);
    		if(K>k||dcmp(K,k))h=mid,r=mid-1;
    		else l=mid+1;
    	}
    	return sk[h];
    }
    void solve(int x,int nsz) {
    	if(nsz==1)return;
    	S=nsz;rt=0;getrt(x);if(nsz==2)rt=x;int t=rt,h=rt;
    	for(re int i=head[rt];i;i=e[i].nxt)vis[i]=1,nsz-=sum[e[i].v];
    	solve(x,nsz);dis[t]=0;top=0;cnt=1;bl[cnt]=x;lp=1;nw=0;
    	for(re int i=head[t];i;i=e[i].nxt) 
    		dis[e[i].v]=w[e[i].v],getdis(e[i].v);
    	while(t!=x) 
    		bl[++cnt]=t,dis[fa[t]]=dis[t]+w[t],t=fa[t];
    	std::sort(st+1,st+top+1,cmp);
    	std::sort(bl+1,bl+cnt+1,cop);
    	for(re int i=1;i<=top;i++) {
    		while(lp<=cnt&&dis[bl[lp]]+dis[st[i]]<=lm[st[i]])ins(bl[lp]),++lp;
    		int j=fid(-1ll*p[st[i]]);
    		LL k=dp[j]+(dis[st[i]]+dis[j])*p[st[i]]+q[st[i]];
    		if(j)dp[st[i]]=min(dp[st[i]],k);
    	}
    	for(re int i=head[h];i;i=e[i].nxt)solve(e[i].v,sum[e[i].v]);
    }
    int main() {
    	n=read(),read();
    	for(re int i=2;i<=n;i++) {
    		fa[i]=read(),add(fa[i],i);w[i]=read();
    		p[i]=read(),q[i]=read(),lm[i]=read();
    	}
    	dp[1]=0;for(re int i=2;i<=n;i++)dp[i]=1e18;
    	mx[0]=n+1;solve(1,n);
    	for(re int i=2;i<=n;i++)printf("%lld
    ",dp[i]);
    	return 0;
    }
    
  • 相关阅读:
    ubuntu系统里常用的几个命令
    Vue中常用知识点demo
    Vue基础知识学习(后端)
    ubuntu内lnmp相关操作命令
    linux下安装lnmp集成环境
    js时间戳与日期格式之间相互转换
    yii2中 选择布局的方式,可以设置不使用布局
    yii2中通过migration创建数据表
    php实现支付宝在线支付和扫码支付demo
    linux下添加用户并将文件夹授权给某一个用户
  • 原文地址:https://www.cnblogs.com/asuldb/p/12250118.html
Copyright © 2020-2023  润新知