bzoj 1758: [Wc2010]重建计划 长链剖分+线段树

题意

有一棵树,每条边有边权。请找一条长度为[L,R]之间的简单路径,使得这段路径的平均长度最大。
n<=100000,vi<=1000000

分析

很显然是先二分答案然后找树上长度在[L,R]之间的最长链。

正解是点分治+单调队列,由于我刚学习了一波长链剖分,便拿这题来练练手。
关于长链剖分
长链剖分有两个作用:一是O(nlogn)预处理后O(1)查询一个点的第k级祖先。二是O(n)维护每个子树以深度为下标的信息。
就拿这题而言,我们需要处理出每棵子树中每个深度的dis的最大值,就可以用长链剖分+线段树来维护。
先递归长儿子,然后继承长儿子的信息,最后合并短儿子的同时顺便维护答案。
一开始写了一个动态开点+手动合并的线段树,虽然理论复杂度仍是O(nlogn),但是常数巨大。
这里有一个很棒的写法,就是预处理出长链剖分序,也就是在求dfs序的时候先递归长儿子。这样的话必然满足长儿子的子树的大小必然大于该点为根的树的最大深度,这样我们就可以把深度信息用一棵线段树来储存,那么递归完长儿子后该点就可以直接继承长儿子的答案。然后这样对于提取短儿子的答案和查询都比较方便。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int N=100005;
const double eps=1e-4;
const double inf=1e15;
const double EPS=1e-8;

int n,L,R,cnt,last[N],dep[N],mx[N],son[N],tim,dfn[N],num[N];
struct edge{int to,next,w;double val;}e[N*2];
struct tree{int l,r;double mx;}t[N*5];
double ans,tmp[N],dis[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

void addedge(int u,int v,int w)
{
    e[++cnt].to=v;e[cnt].w=w;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].w=w;e[cnt].next=last[v];last[v]=cnt;
}

void pre_dfs1(int x,int fa)
{
    dep[x]=dep[fa]+1;mx[x]=dep[x];
    for (int i=last[x];i;i=e[i].next)
    {
        if (e[i].to==fa) continue;
        pre_dfs1(e[i].to,x);
        mx[x]=max(mx[x],mx[e[i].to]);
        if (mx[e[i].to]>mx[son[x]]) son[x]=e[i].to;
    }
}

void pre_dfs2(int x,int fa)
{
    dfn[x]=++tim;
    if (son[x]) pre_dfs2(son[x],x);
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa&&e[i].to!=son[x]) pre_dfs2(e[i].to,x);
}

void build(int d,int l,int r)
{
    t[d].mx=-inf;
    if (l==r)
    {
        num[l]=d;
        return;
    }
    int mid=(l+r)/2;
    build(d*2,l,mid);build(d*2+1,mid+1,r);
}

void modify(int d,int l,int r,int x,double y)
{
    if (l==r)
    {
        t[d].mx=max(t[d].mx,y);
        return;
    }
    int mid=(l+r)/2;
    if (x<=mid) modify(d*2,l,mid,x,y);
    else modify(d*2+1,mid+1,r,x,y);
    t[d].mx=max(t[d*2].mx,t[d*2+1].mx);
}

double query(int d,int l,int r,int x,int y)
{
    if (x>y) return -inf;
    if (l==x&&r==y) return t[d].mx;
    int mid=(l+r)/2;
    return max(query(d*2,l,mid,x,min(y,mid)),query(d*2+1,mid+1,r,max(x,mid+1),y));
}

void dfs(int x,int fa)
{
    modify(1,1,n,dfn[x],dis[x]);
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to==son[x]) dis[e[i].to]=dis[x]+e[i].val,dfs(e[i].to,x);
    for (int i=last[x];i;i=e[i].next)
    {
        if (e[i].to==fa||e[i].to==son[x]) continue;
        dis[e[i].to]=dis[x]+e[i].val;
        dfs(e[i].to,x);
        for (int j=1;j<=mx[e[i].to]-dep[x];j++)
        {
            tmp[j]=t[num[dfn[e[i].to]+j-1]].mx;
            if (j<=R) ans=max(ans,query(1,1,n,max(1,dfn[x]+L-j),min(dfn[x]+mx[x]-dep[x],dfn[x]+R-j))+tmp[j]-2*dis[x]);
        }
        for (int j=1;j<=mx[e[i].to]-dep[x];j++) modify(1,1,n,dfn[x]+j,tmp[j]);
    }
    ans=max(ans,query(1,1,n,dfn[x]+L,min(dfn[x]+mx[x]-dep[x],dfn[x]+R))-dis[x]);
}

int main()
{
    n=read();L=read();R=read();
    for (int i=1;i<n;i++)
    {
        int x=read(),y=read(),z=read();
        addedge(x,y,z);
    }
    pre_dfs1(1,0);
    pre_dfs2(1,0);
    double l=0,r=1000000;t[0].mx=-inf;
    while (r-l>eps)
    {
        double mid=(l+r)/2;
        for (int i=1;i<=cnt;i++) e[i].val=e[i].w-mid;
        ans=-inf;dis[1]=0;
        build(1,1,n);
        dfs(1,0);
        if (ans<=0) r=mid;
        else l=mid;
    }
    printf("%.3lf",l);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值