[bzoj1758][Wc2010]重建计划——长链剖分+线段树+分数规划

题目大意:

给定一颗带权的树,求一条长路在 [ L , R ] [L,R] [L,R]的路径,权值的平均数最大。

思路:

显然先分数规划,二分答案,然后考虑怎么check。
考虑一个简单的树型DP,记 f i , j f_{i,j} fi,j为i子树内距离i为j的点中路径长度和最大是多少,然后一个点可以从它的儿子转移过来,在转移的时候每次记录前缀枚举新添加进来的子树深度计算一遍答案。
这样复杂度 O ( n 2 ) O(n^2) O(n2),发现转移和子树的深度有关,然后第一颗转移的子树只是涉及到了下标的改变,于是考虑长链剖分优化复杂度,每一次直接先找到重儿子继承,然后再枚举每一条轻边,暴力转移每一条轻边上面的链。
由于有长度限制,考虑将每一条长链放到线段树上维护,查询的时候直接在线段树上面查询区间最大值即可。
有的打法需要支持线段树的区间修改,但其实没有这个必要,一个trick就是将 f i , j f_{i,j} fi,j表示为距离i深度为j的点距离根的最长路径长度,计算时再考虑lca的贡献即可。

#include<bits/stdc++.h>
 
#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;
 
using namespace std;
 
void File(){
    freopen("bzoj1758.in","r",stdin);
    freopen("bzoj1758.out","w",stdout);
}
 
template<typename T>void read(T &_){
    _=0; T f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
    _*=f;
}
 
const int maxn=1e5+10;
const double inf=1e18;
int n,Lbound,Rbound,s[maxn],sp;
int beg[maxn],to[maxn<<1],las[maxn<<1],cnte=1;
double w[maxn<<1],val[maxn];
int son[maxn],len[maxn],fa[maxn],dfn[maxn],cnt_dfn;
 
void add(int u,int v,int ww){
    las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v; w[cnte]=ww;
    las[++cnte]=beg[v]; beg[v]=cnte; to[cnte]=u; w[cnte]=ww;
}
 
void dfs1(int u,int fh){
    fa[u]=fh;
    for(int i=beg[u];i;i=las[i]){
        int v=to[i];
        if(v==fh)continue;
        dfs1(v,u);
        if(len[v]+1>len[u]){
            len[u]=len[v]+1;
            son[u]=v;
            val[u]=w[i];
        }
    }
}
 
void dfs2(int u){
    dfn[u]=++cnt_dfn;
    if(son[u])dfs2(son[u]);
    for(int i=beg[u];i;i=las[i]){
        int v=to[i];
        if(v==fa[u] || v==son[u])continue;
        dfs2(v);
    }
}
 
struct Segment_Tree{
#define mid ((l+r)>>1)
#define lc (o<<1)
#define rc (o<<1|1)
#define lson lc,l,mid
#define rson rc,mid+1,r
    double mx[maxn<<2];
    void build(int o,int l,int r){
        mx[o]=-inf;
        if(l==r)return;
        build(lson),build(rson);
    }
    void update(int o,int l,int r,int p,double x){
        if(l==r)mx[o]=max(mx[o],x);
        else{
            if(p<=mid)update(lson,p,x);
            else update(rson,p,x);
            mx[o]=max(mx[lc],mx[rc]);
        }
    }
    double query(int o,int l,int r,int L,int R){
        if(L>R)return -inf;
        if(L<=l && r<=R)return mx[o];
        else{
            double ret=-inf;
            if(L<=mid)ret=max(ret,query(lson,L,R));
            if(R>=mid+1)ret=max(ret,query(rson,L,R));
            return ret;
        }
    }
#undef mid
}T;
 
bool flag;
 
void solve(int u,double sum,double x){
    if(flag)return;
    T.update(1,1,n,dfn[u],sum);
    if(son[u])solve(son[u],sum+val[u]-x,x);
    for(int i=beg[u];i;i=las[i]){
        int v=to[i];
        if(v==fa[u] || v==son[u])continue;
        solve(v,sum+w[i]-x,x);
        if(flag)return;
        REP(j,dfn[v],dfn[v]+len[v]){
            int dis=j-dfn[v]+1,l=Lbound-dis+dfn[u],r=Rbound-dis+dfn[u];
            l=max(l,dfn[u]),r=min(r,dfn[u]+len[u]);
            if(T.query(1,1,n,j,j)+T.query(1,1,n,l,r)-sum*2>0)flag=true;
            if(flag)return;
        }
        REP(j,dfn[v],dfn[v]+len[v]){
            int p=j-dfn[v]+1+dfn[u];
            T.update(1,1,n,p,T.query(1,1,n,j,j));
        }
    }
    int l=dfn[u]+Lbound,r=min(dfn[u]+len[u],dfn[u]+Rbound);
    if(T.query(1,1,n,l,r)-sum>0)flag=true;
}
 
bool judge(double x){
    T.build(1,1,n);
    flag=0;
    solve(1,0,x);
    return flag;
}
 
int main(){
    //File();
    read(n),read(Lbound),read(Rbound);
    int u,v,ww;
    REP(i,1,n-1)read(u),read(v),read(ww),add(u,v,ww);
    dfs1(1,0);
    dfs2(1);
    double l=0,r=1e6;
    while(r-l>1e-4){
        double mid=(l+r)/2;
        if(judge(mid))l=mid;
        else r=mid;
    }
    printf("%.3lf\n",l);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值