Problem
Solution
二分答案,把路径权值都减去mid,那么就变成了能不能选出一条长度
[
L
,
R
]
[L,R]
[L,R]权值大于等于0的路径。
考虑点分治,那么我们就只需要考虑拼接两棵子树。这是一个有长度限制以深度为下标的dp。这里用单调队列优化。记录
f
[
i
]
f[i]
f[i] 表示之前遍历过的子树中深度为
i
i
i 到根的最大权值,
g
[
i
]
g[i]
g[i] 为当前的,单调队列优化拼接即可。
但是仔细想想,如果 f f f 如果很长, g g g 却很短,如此构造可以使点分治退化成 O ( n 2 log n ) O(n^2\log n) O(n2logn)。为了复杂度正确,必须使 f , g f,g f,g的长度与当前处理的子树大小相关,那么我们按子树最深的深度从小到大处理。这样的复杂度就不会退化。
时间复杂度 O ( n log n log v ) O(n\log n \log v) O(nlognlogv)
然后卡常才是这道题的恶心之处。笔者的代码用了这么些卡常的方法:
- 预处理出点分树,并把子树先排序好,用vector存起来
- 减少调用dfs
- 当前树的大小小于等于 L L L 时直接退出
- 减少实数运算
由于预处理了点分树,所以你还可以通过逆序枚举分治重心来应对毒瘤数据。
Code
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#define mk(x,y) make_pair(x,y)
#define fr first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=100010;
const double eps=1e-7,INF=1e9;
template <typename Tp> int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> void read(Tp &x)
{
x=0;char ch=getchar();int f=0;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') ch=getchar(),f=1;
while(ch>='0'&&ch<='9') x=x*10+(ch-'0'),ch=getchar();
if(f) x=-x;
}
struct data{
int v,nxt;double w;
data(const int _v=0,const double _w=0.0,const int _nxt=0){v=_v;w=_w;nxt=_nxt;}
}edge[maxn<<1];
int n,L,R,p,lim,now,head[maxn],dep[maxn],dsz[maxn],tp[maxn];
double ans,f[maxn],g[maxn],w[maxn];
deque<int> q;
vector<pii> son[maxn];
vector<pii>::iterator itr;
namespace Build{
const static int INF=0x3f3f3f3f;
int p,sn,rt,top,sz[maxn],mx[maxn],dis[maxn],mark[maxn],stk[maxn];
int cmp(const int &a,const int &b){return dis[a]<dis[b];}
void getrt(int x,int pre)
{
sz[x]=1;mx[x]=dis[x]=0;
for(int i=head[x];i;i=edge[i].nxt)
if(!mark[edge[i].v]&&edge[i].v^pre)
{
getrt(edge[i].v,x);
sz[x]+=sz[edge[i].v];
getmax(mx[x],sz[edge[i].v]);
getmax(dis[x],dis[edge[i].v]);
}
getmax(mx[x],sn-sz[x]);
if(mx[x]<mx[rt]) rt=x;
++dis[x];
}
void dfs(int x,int now)
{
mark[x]=1;getrt(x,x);dep[x]=now;dsz[x]=sz[x];top=0;
for(int i=head[x];i;i=edge[i].nxt)
if(!mark[edge[i].v])
stk[++top]=edge[i].v;
sort(stk+1,stk+top+1,cmp);
for(int i=1;i<=top;i++) son[x].push_back(mk(stk[i],dis[stk[i]]));
for(int i=head[x];i;i=edge[i].nxt)
if(!mark[edge[i].v])
{
sn=sz[edge[i].v];rt=0;
getrt(edge[i].v,edge[i].v);
dfs(rt,now+1);
}
}
}
int cmp(const int &a,const int &b){return dep[a]<dep[b];}
void insert(int u,int v,double w)
{
edge[++p]=data(v,w,head[u]);head[u]=p;
edge[++p]=data(u,w,head[v]);head[v]=p;
}
void input()
{
int u,v,w;
read(n);read(L);read(R);
for(int i=1;i<n;i++)
{
read(u);read(v);read(w);
insert(u,v,(double)w);
}
Build::mx[0]=Build::INF;Build::sn=n;
Build::getrt(1,1);Build::dfs(Build::rt,1);
for(int i=1;i<=n;i++) tp[i]=i;
sort(tp+1,tp+n+1,cmp);
}
void dfs2(int x,int pre,int deep,double sum)
{
getmax(g[deep],sum);
for(int i=head[x];i;i=edge[i].nxt)
if(dep[edge[i].v]>now&&edge[i].v^pre)
dfs2(edge[i].v,x,deep+1,sum+edge[i].w);
}
int work(int x)
{
if(dsz[x]<=L) return 0;
int lf=0,lg,ptr,tot=(int)son[x].size();
now=dep[x];
for(int i=head[x];i;i=edge[i].nxt)
if(dep[edge[i].v]>dep[x])
w[edge[i].v]=edge[i].w;
itr=son[x].end()-1;
for(int i=itr->se;i;i--) f[i]=g[i]=-INF;
for(itr=son[x].begin();itr!=son[x].end();++itr)
{
lg=itr->se;dfs2(itr->fr,x,1,w[itr->fr]);ptr=min(lf,R-1);
if(lf+lg>=L)
{
while(!q.empty()) q.pop_front();
for(int i=1;i<=lg;i++)
{
while(ptr&&i+ptr>=L)
{
while(!q.empty()&&f[q.back()]<f[ptr]) q.pop_back();
q.push_back(ptr);--ptr;
}
while(!q.empty()&&i+q.front()>R) q.pop_front();
if(!q.empty()&&g[i]+f[q.front()]>=0) return 1;
}
}
lf=lg;
for(int j=1;j<=lf;j++) getmax(f[j],g[j]),g[j]=-INF;
}
for(int i=min(R,lf);i>=L;i--) if(f[i]>=0) return 1;
return 0;
}
int check(double x)
{
for(int i=n;i>=1;i--)
if(work(tp[i]))
return 1;
return 0;
}
int main()
{
input();
double l=0,r=1e6,mid;
while(l+1e-4<=r)
{
mid=(l+r)/2;
for(int i=1;i<=p;i++) edge[i].w-=mid;
if(check(mid)) l=mid,ans=mid;
else r=mid;
for(int i=1;i<=p;i++) edge[i].w+=mid;
}
printf("%.3lf\n",ans);
return 0;
}