题意
有一棵树,每条边有边权。请找一条长度为[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;
}