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

时间:2021-08-03 20:01:36

题目大意:

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

思路:

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