JZOJ 5403. 【NOIP2017提高A组模拟10.8】Lost My Music

题目

这里写图片描述

题解

这个式子的分子不是单调的,分母也不是固定的,那怎么办?
这个式子是不是很像斜率?(直接解决上面两大难点)
没错,如果这个数据结构不是以树的形式呈现,而是以数列的形式呈现,那很好做。
如果是一个数列,我们直接维护斜率为 cucvdudv 的上凸壳。
为什么?
维护上凸壳的意义是,当加入一个点(x,y)时,题目要求要找到该序列之前的某个点(x0,y0) (x0<x) ,使(x0,y0)与(x,y)使在(x’,y’)与(x,y)的斜率中是最小的。
如果(x,y)在此上凸壳内,那么(x,y)的前一个点是(x0,y0)。
所以要维护一个上凸壳。
但是题目问的是树上而不是序列上。
那么可以看成是很多条从根节点到某个叶节点的序列。
那么怎么弹栈?
有dalao(LYD)想到暴力弹栈。想法很美好,但是暴力弹栈最坏时间复杂度可以到 O(n2)
那么我们倍增弹栈。
为什么这样想?结合图来讲。
这里写图片描述
假设x-z是一个序列,当我们暴力建栈的时候,x-y中有的点被弹掉(图中的红色”x”部分),那么x-z中x-y的点该弹掉的也都会被弹掉。
所以我们进行倍增,pre[x][i]表示在“凸壳”关系树上的x的2^i祖先,那么我们现在要求pre[x][0]。
我们现在要筛选掉fa[x]的序列中的该淘汰的点,也就是说(下图)
这里写图片描述
那么设v表示当前倍增到的点,v与pre[v][0]比较一下他们各自与x连成的直线的斜率,如果v的斜率较大则v到x之前的点都要被淘汰。要跳到v的前面去.
对这道题的心得:
①看到比例式可以去考虑斜率,即维护凸壳。
②考虑j状态比k状态优会出现哪个式子,如果 wi 没有单调性,或不能够拆成只有j和k的式子,那么考虑直接用题目给的比例式。
③对于序列问题,如果要放在树上做,那么先考虑放在序列上做。
④如果序列之间有重叠的部分,且暴力重构序列的重叠的那一段操作的结果相同,那么考虑用更优的方法保留之前重叠的部分,然后后面的序列才重构。比如现在这题的倍增弹栈。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 500010
#define LL long long
#define DB double
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    int to,next;
};note edge[N];
int i,j,k,l,n,m,cnt,tot;
int now,t,w,x;
int c[N],fa[N],pre[N][20];
int head[N],dep[N],qu[N];
int ans[N];
DB temp,temp1,temp2;
int c1,c2,d1,d2;
int read(){
    int res=0,fh=1;char ch;
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return res;
}
void lb(int x,int y){edge[++tot].to=y;edge[tot].next=head[x];head[x]=tot;}
int main(){
    freopen("lost.in","r",stdin);
    freopen("lost.out","w",stdout);
    n=read();
    fo(i,1,n)c[i]=read();
    fo(i,2,n)fa[i]=read(),lb(fa[i],i);
    t=0,w=1;qu[1]=1;dep[1]=1;
    while(t<w){
        now=qu[++t];
        dep[now]=dep[fa[now]]+1;
        if(now!=1){
            x=fa[now];
            fd(i,19,0){
                if(pre[x][i]<=1)continue;
                c1=c[pre[x][i]]-c[now];
                c2=c[pre[pre[x][i]][0]]-c[now];
                d1=dep[now]-dep[pre[x][i]];
                d2=dep[now]-dep[pre[pre[x][i]][0]];
                temp1=c1/(DB)d1;
                temp2=c2/(DB)d2;
                if(temp1>=temp2)x=pre[pre[x][i]][0];
            }
            if(x>1){
                c1=c[x]-c[now];
                c2=c[pre[x][0]]-c[now];
                d1=dep[now]-dep[x];
                d2=dep[now]-dep[pre[x][0]];
                temp1=c1/(DB)d1;
                temp2=c2/(DB)d2;
                if(temp1>=temp2)x=pre[x][0];
            }
            ans[now]=pre[now][0]=x;
            fo(i,1,19)pre[now][i]=pre[pre[now][i-1]][i-1];
        }
        for(i=head[now];i;i=edge[i].next)
            if(!dep[edge[i].to])qu[++w]=edge[i].to;
    }
    fo(i,2,n){
        temp=(c[ans[i]]-c[i])/(DB)(dep[i]-dep[ans[i]]);
        printf("%.10lf\n",temp);
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值