- 给定一棵(n)个点的树,有点权和边权。
- 定义两点间的距离为两点间边权和的(frac32)次方。
- 求这棵树的带权重心。
- (nle2 imes10^5)
下凸函数
显然,对于一条树上路径([a,b])中的点(x),(x)与另一点(i)之间的距离(d(x,i))是一个下凸函数。
考虑对于一个点,(f(x)=sum_{i=1}^nd(x,i) imes a(x))是凸函数之和,那么它仍然是凸函数。
所以,如果从树的重心向外扩展,(f(x))的变化必然是递增的。
点分治
考虑对于一条链的情况,我们可以采取二分。
把二分扩展到树上,就变成了点分治。注意这里的点分治和通常所说的点分治并不完全一样。
对于当前的根节点,从它向子节点走,最多只会有一个子节点可能使得(f(x))变大。
至于如何判断是否变大,显然不能直接暴力求解。
我们可以求出该函数的导数。设一个子节点的导数为(p(y)),总和为(s),如果((s-p(y))-p(y)=s-2p(y)<0),就说明向这个子节点走可能使得(f(x))变小,那么递归继续做即可。
代码
#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 200000
#define DB double
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,a[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}e[N<<1];
namespace DotSolver//点分治
{
int ans,rt,Sz[N+5],Mx[N+5],used[N+5];DB cur,res,p[N+5];
I void GetRt(CI x,CI lst,RI s)//找重心
{
Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
!used[e[i].to]&&(GetRt(e[i].to,x,s),Sz[x]+=Sz[e[i].to],Mx[x]=max(Mx[x],Sz[e[i].to]));
Mx[x]=max(Mx[x],s-Sz[x]),Mx[x]<Mx[rt]&&(rt=x);
}
I void Calc(CI x,CI lst,CI d,DB& p)//遍历子树进行统计
{
cur+=pow(d,1.5)*a[x],p+=1.5*pow(d,0.5)*a[x];//cur统计答案,p统计导数
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(Calc(e[i].to,x,d+e[i].v,p),0);
}
I void Work(RI x)//求解当前子树
{
RI i,y;DB s=cur=0;used[x]=1;
for(i=lnk[x];i;i=e[i].nxt) p[y=e[i].to]=0,Calc(y,x,e[i].v,p[y]),s+=p[y];//s统计导数总和
for((!ans||cur<res)&&(ans=x,res=cur),i=lnk[x];i;i=e[i].nxt)//更新答案
if(!used[e[i].to]&&s-2*p[y=e[i].to]<0) return GetRt(y,rt=0,Sz[y]),Work(rt);//走向可能使答案变大的点
}
I void Solve() {Mx[0]=1e9,GetRt(1,rt=0,n),Work(rt),printf("%d %.10lf
",ans,res);}
}
int main()
{
RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
RI x,y,z;for(i=1;i^n;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
return DotSolver::Solve(),0;
}