所有边减去mid,看树上有没有边权和大于等于0的路径。可以用点分治。
枚举当前分治子树内的结点u,那么我们就要找到之前深度范围为[L-dep[u],R-dep[u]]的最大值。(到当前重心的边权和)
如果用线段树维护,查询和更新都要套一个log,时间要炸。
我们发现如果dep是单增的,那么就相当于是一个滑动窗口问题。可以用一个单调队列维护。
我们用深度记录这个单调队列。我们只要保证深度单调,距离单减就好,因为要维护距离的最大值。用深度判断是否符合[L,U]的长度要求。其实就是判断队列内元素是否在窗口内。
如果我们枚举的深度单增,那么另一边的深度单减,先保证下限L,再保证上限U。反之先保证R,再保证L。
然而每次滑动窗口的复杂度是由 max(之前的最大深度,当前子树大小) 决定的。点分治只能保证子树的大小,不能保证深度。于是网上大部分题解可以用一个扫把图卡成n²log,233。
然而我们只要先把子树按照最大深度排个序就好了。
具体实现的时候,就是先从当前重心dfs,得到每个子树的最大深度以及每种深度的最大权值。
用f[i]表示之前已经搜过的子树中,深度为i的最大边权和。
用g[i]表示现在正在枚举的子树中,深度为i的最大边权和。
用now_mxdp[u]表示u子树中最大深度,用predep表示之前已经搜索过的子树中可以达到的最大深度。
然后二分mid,在f和g数组中暴力把(mid*深度)减去。
滑动窗口做完之后记得把f数组和g数组给加回去。然后还要更新f数组。
每搜完一个子树需要把g数组给重置。每搜完一个重心需要把f数组给重置。
大概就是这样。最后,注意精度。
二分写点分里面比较好,因为可以不停更新二分左边界。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const double inf=1e9;
const double eps=1e-9;
int Head[maxn],Next[maxn<<1],V[maxn<<1],W[maxn<<1],cnt=0;
int dep[maxn],siz[maxn],maxsub[maxn],vis[maxn],tot=0;
int now_mxdp[maxn],to[maxn];
int n,L,U,root=0,SZ,a,b,c;
int nowdep,predep,head,tail;
double dis[maxn],up=-inf,f[maxn],g[maxn],ans=inf;
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
inline void add(int u,int v,int w){
++cnt;
Next[cnt]=Head[u];
V[cnt]=v,W[cnt]=w;
Head[u]=cnt;
}
inline void getroot(int u,int f){
maxsub[u]=0,siz[u]=1;
for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]]){
getroot(V[i],u);
maxsub[u]=max(maxsub[u],siz[V[i]]);
siz[u]+=siz[V[i]];
}
maxsub[u]=max(maxsub[u],SZ-siz[u]);
if(maxsub[root]>maxsub[u]) root=u;
}
void get_dis(int u,int f,int dep,double val){
nowdep=max(nowdep,dep),dis[u]=val;
for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]])
get_dis(V[i],u,dep+1,(double)W[i]+val);
}
void get_g(int u,int f,int dep){
g[dep]=max(g[dep],dis[u]);
for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]] && V[i]!=f)
get_g(V[i],u,dep+1);
}
bool check(double M,bool ret=0){
for(int i=1;i<=nowdep;++i) g[i]-=(double)i*M;
for(int i=1;i<=predep;++i) f[i]-=(double)i*M;
deque<int> Q;int now=predep;
for(int i=0;i<=nowdep&&(!ret);++i){
while(((now+i)>=L)&&(now>=0)){
while((!Q.empty())&&(f[Q.back()]<f[now])) Q.pop_back();
Q.push_back(now--);
}
while((!Q.empty())&&((Q.front()+i)>U)) Q.pop_front();
if((!Q.empty())&&(f[Q.front()]+g[i]>=0)) ret=1;
}
for(int i=1;i<=nowdep;++i) g[i]+=(double)i*M;
for(int i=1;i<=predep;++i) f[i]+=(double)i*M;
return ret;
}
bool cmp(const int &a,const int &b){return now_mxdp[a]<now_mxdp[b];}
void solve(int u){
vis[u]=1,predep=0,dis[u]=0.0,tot=0;
for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]){
to[++tot]=V[i],nowdep=0;
get_dis(V[i],u,1,(double)W[i]);
now_mxdp[V[i]]=nowdep;
}
sort(to+1,to+tot+1,cmp);
for(int i=1;i<=tot;++i){
nowdep=now_mxdp[to[i]];
get_g(to[i],u,1);
double l=ans,r=up;
while(r-l>eps){
double mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
ans=r,predep=max(predep,nowdep);
for(int i=1;i<=nowdep;++i) f[i]=max(f[i],g[i]),g[i]=-inf;
}
for(int i=1;i<=predep;++i) f[i]=-inf;
for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]])
root=0,SZ=siz[V[i]],getroot(V[i],u),solve(root);
}
int main(){
maxsub[0]=SZ=n=read(),L=read(),U=read();
for(int i=1;i<n;++i){
a=read(),b=read(),c=read();
ans=min(ans,double(c));
up=max(up,double(c));
add(a,b,c),add(b,a,c);
}
for(int i=1;i<=n;++i) g[i]=f[i]=-inf;
getroot(1,0),solve(root);
printf("%.3lf\n",ans);
}
如果二分写在点分治外面,要记一下重心,免得每次都要求重心。代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const double inf=1e9;
const double eps=1e-9;
int Head[maxn],Next[maxn<<1],V[maxn<<1],W[maxn<<1],cnt=0;
int dep[maxn],siz[maxn],maxsub[maxn],vis[maxn],tot=0;
int now_mxdp[maxn],to[maxn],rt[maxn<<1];
int n,L,U,root=0,RT=0,SZ,a,b,c;
int nowdep,predep,head,tail;
double dis[maxn],down=inf,up=-inf,f[maxn],g[maxn],ans,l,r;
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
inline void add(int u,int v,int w){
++cnt;
Next[cnt]=Head[u];
V[cnt]=v,W[cnt]=w;
Head[u]=cnt;
}
inline void getroot(int u,int f){
maxsub[u]=0,siz[u]=1;
for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]]){
getroot(V[i],u);
maxsub[u]=max(maxsub[u],siz[V[i]]);
siz[u]+=siz[V[i]];
}
maxsub[u]=max(maxsub[u],SZ-siz[u]);
if(maxsub[root]>maxsub[u]) root=u;
}
void get_dis(int u,int f,int dep,double val,double M){
nowdep=max(nowdep,dep),dis[u]=val;
for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]])
get_dis(V[i],u,dep+1,(double)W[i]-M+val,M);
}
void get_g(int u,int f,int dep){
g[dep]=max(g[dep],dis[u]);
for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]] && V[i]!=f)
get_g(V[i],u,dep+1);
}
bool check(){
deque<int> Q;int now=predep;
for(int i=0;i<=nowdep;++i){
while(((now+i)>=L)&&(now>=0)){
while((!Q.empty())&&(f[Q.back()]<f[now])) Q.pop_back();
Q.push_back(now--);
}
while((!Q.empty())&&((Q.front()+i)>U)) Q.pop_front();
if((!Q.empty())&&(f[Q.front()]+g[i]>=0)) return true;
}
return false;
}
bool cmp(const int &a,const int &b){return now_mxdp[a]<now_mxdp[b];}
bool solve(int u,double M,bool flag=0){
vis[u]=1,predep=dis[u]=tot=0;
for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]){
to[++tot]=V[i],nowdep=0;
get_dis(V[i],u,1,(double)W[i]-M,M);
now_mxdp[V[i]]=nowdep;
}
sort(to+1,to+tot+1,cmp);
for(int i=1;((i<=tot)&&(!flag));++i){
nowdep=now_mxdp[to[i]],get_g(to[i],u,1);
if(check()) flag=1;
predep=max(predep,nowdep);
for(int i=1;i<=nowdep;++i) f[i]=max(f[i],g[i]),g[i]=-inf;
}
for(int i=1;i<=predep;++i) f[i]=-inf;
if(flag) return true;
for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]){
if(rt[i]){
if(solve(rt[i],M))
return true;
}
else{
root=0,SZ=siz[V[i]],getroot(V[i],u),rt[i]=root;
if(solve(rt[i],M))
return true;
}
}
return false;
}
int main(){
freopen("test.in","r",stdin);
freopen("ans.out","w",stdout);
maxsub[0]=SZ=n=read(),L=read(),U=read();
for(int i=1;i<n;++i){
a=read(),b=read(),c=read();
ans=min(ans,double(c));
up=max(up,double(c));
add(a,b,c),add(b,a,c);
}
for(int i=1;i<=n;++i) f[i]=g[i]=-inf;
r=up,l=ans;
getroot(1,0),RT=root;
while(r-l>eps){
double mid=(l+r)/2.0;
for(int i=1;i<=n;++i) vis[i]=0;
if(solve(RT,mid)) ans=mid,l=mid;
else r=mid;
}
printf("%.3lf\n",ans);
}