【洛谷4292】[WC2010] 重建计划(分数规划+点分治)
- 给定一棵\(n\)个点的树,每条边有一个边权。
- 要求找到一条边数在\([L,U]\)范围内的路径,使得边权平均值最大。
- \(n\le10^5\)
分数规划
这种求平均值的问题显然就分数规划啊。
考虑二分一个平均值\(mid\),然后给所有边权减去\(mid\)。
现在就是要求是否存在一条边数在\([L,U]\)范围内的路径,使得边权和大于等于\(0\)。
点分治+单调队列
总体而言还是比较套路的,对于每个点存下从它向每个子树里的路径。
这种一个取值范围的问题显然只要开个桶维护每种深度的最大值,然后用单调队列即可维护。
但要注意一个细节,就是我们在每个子树上花费的复杂度,不仅是它的深度,还有之前已处理过的子树的最大深度,所以我们需要把所有子树按最大深度排序之后再依次处理。
还有就是这题比较卡常,因为树的形态不变,所以我们每次点分治找到分治重心不变,可以预先存下来建出点分树。
代码:\(O(nlog^2n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB double
#define eps 1e-4
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
#define nadd(x,y) (ne[++nee].nxt=nlnk[x],ne[nlnk[x]=nee].to=y)
using namespace std;
int n,L,U;int ee,lnk[N+5];struct edge {int to,nxt,v;}e[N<<1];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
namespace T//点分治
{
DB mid,ev[N+5],u[N+5],v[N+5];int Rt,pd[N+5],nee,nlnk[N+5];edge ne[N+5];
int md[N+5];I bool cmp(CI x,CI y) {return md[x]<md[y];}
int rt,Sz[N+5],Mx[N+5],ud[N+5];I void GetRt(CI x,CI lst,RI sz)//找重心
{
Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
!ud[e[i].to]&&(GetRt(e[i].to,x,sz),Sz[x]+=Sz[e[i].to],Mx[x]=max(Mx[x],Sz[e[i].to]));
(Mx[x]=max(Mx[x],sz-Sz[x]))<Mx[rt]&&(rt=x);
}
I void GD(CI x,CI id,CI lst=0,CI d=1) {md[id]=max(md[id],d);//求最大深度
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!ud[e[i].to]&&(GD(e[i].to,id,x,d+1),0);}
vector<int> s[N+5];I void Build(RI x)//建点分树
{
RI i,j,t=0;for(ud[x]=1,i=lnk[x];i;i=e[i].nxt) !ud[e[i].to]&&(GD(e[i].to,e[i].to),
s[x].push_back(e[i].to),rt=0,GetRt(e[i].to,x,Sz[e[i].to]),nadd(x,rt),Build(rt),0);//向下一分治重心连边
sort(s[x].begin(),s[x].end(),cmp);//把子节点按最大深度排序
}
I void dfs(CI x,DB g=0,CI lst=0,CI d=1) {v[d]=max(v[d],g);//记录每种深度的最大值
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!pd[e[i].to]&&(dfs(e[i].to,g+e[i].v-mid,x,d+1),0);}
int q[N+5];I bool Check(RI x)//检验答案
{
RI i;vector<int>::iterator it;if(pd[x]=1,s[x].empty()) return 0;//如果子树内没有其他点直接结束
for(i=lnk[x];i;i=e[i].nxt) ev[e[i].to]=e[i].v;for(i=1;i<=md[*--s[x].end()];++i) u[i]=-1e9;//初始化
RI H,T,mx=0;for(it=s[x].begin();it!=s[x].end();mx=md[*it],++it)//按深度从小到大枚举子节点
{
for(i=1;i<=md[*it];++i) v[i]=-1e9;dfs(*it,ev[*it]-mid);//清空后dfs
for(H=1,T=0,i=min(mx,U);i>=L;q[++T]=i--) W(H<=T&&u[q[T]]<=u[i]) --T;//用之前子树的信息初始化单调队列
for(i=1;i<=min(md[*it],U);++i)//枚举当前子树内的深度
{
if(H<=T&&i+q[H]>U&&++H,i<=L&&L-i<=mx) {W(H<=T&&u[q[T]]<=u[L-i]) --T;q[++T]=L-i;}//弹出队首不合法元素,加入队尾新元素
if(H<=T&&v[i]+u[q[H]]>=0) return 1;//检验
}
for(i=1;i<=md[*it];++i) u[i]=max(u[i],v[i]);//更新总信息
}
for(i=nlnk[x];i;i=ne[i].nxt) if(Check(ne[i].to)) return 1;return 0;//递归子树中的分治重心
}
}
int main()
{
RI i,x,y,z;for(read(n,L,U),i=1;i^n;++i) read(x,y,z),add(x,y,z),add(y,x,z);
T::Mx[0]=1e9,T::GetRt(1,0,n),T::Build(T::Rt=T::rt);DB l=0,r=1e6;//初始建点分树预处理
W(r-l>eps) {for(i=1;i<=n;++i) T::pd[i]=0;//分数规划
T::mid=(l+r)/2,T::Check(T::Rt)?l=T::mid:r=T::mid;}return printf("%.3lf\n",r),0;
}
待到再迷茫时回头望,所有脚印会发出光芒