题意
N<=100000,1<=L<=U<=N-1,Vi<=1000000
分析
参照The_Virtuoso的题解。
这题算是长链剖分最经典的一道题了。
首先要找一个最优方案,直接DP显然不好做,那么考虑二分答案然后验证,因为是浮点数,要注意精度问题。
假设当前二分的答案是k,判断答案是否满足时原式也就转化成了\(\frac{\sum v_i}{|S|}\ge k\),将分母移到不等式右边得到\(\sum v_i \ge k∗|S|\)
将右边的部分移到左边就变成了\(\sum v_i−k∗|S| \ge 0\)
因为\(v_i\)的个数就是\(|S|\),因此将\(k*|S|\)放到\(Σ\)里面,判断就变成了\(\sum (v_i−k) \ge 0\)
每次判断只要把每条边的边权和减\(k\),再判断能否有一条路径边权和大于等于0就好了。
怎么判断呢?很容易想到\(O(n^2)\)dp,设f[i][j]表示i子树中与i距离为j的链的边权和最大值,枚举另一棵子树找到链长在[L-j,R-j]之内的边权最大值。
\(O(n^2)\)dp显然不行,但观察到dp是可合并的以深度为下标的转移方程,因此可以用长链剖分优化成\(O(n)\)。
怎么优化呢?
首先对整棵树长链剖分求出树剖序,再把树剖序架到线段树上,因为整棵树是由所有长链组成的,每条长链因为是优先遍历所以在树剖序上是连续的一段。
也就是说树剖序上的每一段都是树剖出的一条长链。那么每个点子树中每个深度的信息就可以都存到这个点往下的长链上。
当做树形DP时,每个点对于重儿子回溯时不做任何操作,直接继承;当轻儿子回溯时枚举轻儿子每个深度的边权和最大值,在长链上找到对应区间求最大值来更新答案。
然后再把这个轻儿子的信息合并到长链上。因为长链上存的是之前所有遍历过的子树合并后的信息,所以相当于每个点子树中有用的信息都在这个点往下的长链上。最后别忘了考虑从上到下的每条直链。
这样DP是\(O(n)\)的,再加上线段树的\(O(log)\)和二分的\(O(log)\)一共是\(O(n \log^2n)\)。
至于这样DP为什么是\(O(n)\)的?
因为每个点对重儿子是直接继承的,而每个点需要被DP当且仅当它是轻儿子时,这时它一定是一个长链的链头,DP的时间复杂度是这个点往下的长链长度,那么DP的总复杂度就是每条长链的链长总和,也就是\(O(n)\)。
代码
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;
rg char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') w=-1;
ch=getchar();
}
while(isdigit(ch))
data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x){
return x=read<T>();
}
typedef long long ll;
co int N=1e5+1;
co double INF=1e11,eps=1e-4;
int n,L,R;
int head[N],to[N*2],v[N*2],nx[N*2],tot;
int fa[N],dep[N],mxd[N],son[N],pos[N],dfn;
void add(int x,int y,int w){
to[++tot]=y,v[tot]=w,nx[tot]=head[x],head[x]=tot;
}
void dfs1(int x){
dep[x]=dep[fa[x]]+1,mxd[x]=dep[x];
for(int i=head[x];i;i=nx[i]){
if(to[i]==fa[x]) continue;
fa[to[i]]=x;
dfs1(to[i]);
mxd[x]=std::max(mxd[x],mxd[to[i]]);
if(mxd[to[i]]>mxd[son[x]]) son[x]=to[i];
}
}
void dfs2(int x){
pos[x]=++dfn;
if(!son[x]) return;
dfs2(son[x]);
for(int i=head[x];i;i=nx[i]){
if(to[i]==fa[x]||to[i]==son[x]) continue;
dfs2(to[i]);
}
}
int id[N];
double ans,dis[N],tmp[N],val[N*2],mxv[N*4];
void build(int x,int l,int r){
mxv[x]=-INF;
if(l==r){
id[l]=x;
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
}
void change(int x,int l,int r,int p,double v){
mxv[x]=std::max(mxv[x],v);
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) change(x<<1,l,mid,p,v);
else change(x<<1|1,mid+1,r,p,v);
}
double query(int x,int l,int r,int ql,int qr){
if(ql>qr) return -INF;
if(ql<=l&&r<=qr) return mxv[x];
int mid=(l+r)>>1;
if(qr<=mid) return query(x<<1,l,mid,ql,qr);
if(ql>mid) return query(x<<1|1,mid+1,r,ql,qr);
return std::max(query(x<<1,l,mid,ql,qr),query(x<<1|1,mid+1,r,ql,qr));
}
void tree_dp(int x){
change(1,1,n,pos[x],dis[x]);
for(int i=head[x];i;i=nx[i]) if(son[x]==to[i]){
dis[son[x]]=dis[x]+val[i];
tree_dp(son[x]);
break;
}
for(int i=head[x];i;i=nx[i]){
if(to[i]==fa[x]||to[i]==son[x]) continue;
dis[to[i]]=dis[x]+val[i];
tree_dp(to[i]);
for(int j=1;j<=mxd[to[i]]-dep[x];++j){
tmp[j]=mxv[id[pos[to[i]]+j-1]];
if(j<=R) ans=std::max(ans,query(1,1,n,std::max(pos[x],pos[x]+L-j),std::min(pos[x]+mxd[x]-dep[x],pos[x]+R-j))+tmp[j]-2*dis[x]);
}
for(int j=1;j<=mxd[to[i]]-dep[x];++j)
change(1,1,n,pos[x]+j,tmp[j]);
}
ans=std::max(ans,query(1,1,n,pos[x]+L,std::min(pos[x]+mxd[x]-dep[x],pos[x]+R))-dis[x]);
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(L),read(R);
for(int i=1;i<n;++i){
int x=read<int>(),y=read<int>(),w=read<int>();
add(x,y,w),add(y,x,w);
}
dfs1(1),dfs2(1);
double l=0,r=1e6;
while(r-l>eps){
double mid=(l+r)/2;
for(int i=1;i<=tot;++i)
val[i]=v[i]-mid;
build(1,1,n);
ans=-INF,dis[1]=0;
tree_dp(1);
if(ans<0) r=mid;
else l=mid;
}
printf("%.3lf",l);
return 0;
}