【Codeforces 566C】Logistical Questions
点分治。
题目大意:
给定一棵树,带点权和边权。令 $\mathrm{dist}(u,v)$ 表示树上点 $u$ 和 $v$ 之间的边权和。要求找到点 $s$,并最小化下面的式子的值。
题解:
考虑一条链的情况。这种情况我们可以把点放到坐标轴上,其距离就是坐标的差。
函数 $f(x)=c\cdot|x-x_0|^{1.5}$ 是下凸函数。而凸函数的和函数仍是凸函数。
因此和函数 $F(x)=\sum_{i=1}^n f_i(x)$ 也是凸函数。求其最值,我们可以用求导二分零点的方法。
考虑在树上的情况。
我们对每条边考虑,对于重心在这条边上的任意情况,其和函数都和链上的情况一样,仍是凸函数。因此最优的答案的重心是唯一的。
考虑在树上找这个重心,每次通过对每条边考虑,求出能使答案变小的方向继续找。
树上的问题可以使用点分治,判断往哪个方向走,就对每条边的和函数求导,往导数为负的方向走即可。
这里每次求导可以通过 $O(n)$ 的 dfs 处理出全部信息来完成。
时间复杂度 $O(n\log n)$。
Code:
#include<cstdio>
#include<cmath>
const int N=2e5+6;
int n,w[N],head[N],cnt,anf,rt,mxd[N],sz[N],all;
bool vis[N];
double ans=1e19,ds,D[N],res;
struct edge{
int to,nxt,dis;
}e[N<<1];
void getrt(int now,int pre){
sz[now]=1,mxd[now]=0;
for(int i=head[now];i;i=e[i].nxt)
if(!vis[e[i].to]&&pre!=e[i].to){
getrt(e[i].to,now);
if(mxd[now]<sz[e[i].to])mxd[now]=sz[e[i].to];
sz[now]+=sz[e[i].to];
}
if(mxd[now]<all-sz[now])mxd[now]=all-sz[now];
if(!rt||mxd[rt]>mxd[now])rt=now;
}
void dfs(int now,int pre,double&D,int dist){
const double sq=sqrt(dist);
res+=sq*dist*w[now],D+=sq*w[now],ds+=sq*w[now];
for(int i=head[now];i;i=e[i].nxt)if(e[i].to!=pre)
dfs(e[i].to,now,D,dist+e[i].dis);
}
void solve(int now){
vis[now]=1;
ds=res=0;
for(int i=head[now];i;i=e[i].nxt){
D[e[i].to]=0;
dfs(e[i].to,now,D[e[i].to],e[i].dis);
}
if(ans>res)ans=res,anf=now;
for(int i=head[now];i;i=e[i].nxt)
if(ds-2*D[e[i].to]<0){
if(vis[e[i].to])break;
all=sz[e[i].to]<sz[now]?sz[e[i].to]:all-sz[now];
rt=0;
getrt(e[i].to,now);
solve(rt);
break;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",w+i);
for(int i=1;i<n;++i){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[++cnt]=(edge){v,head[u],w},head[u]=cnt;
e[++cnt]=(edge){u,head[v],w},head[v]=cnt;
}
all=n;
rt=0;
getrt(1,0);
solve(rt);
printf("%d %.10e\n",anf,ans);
return 0;
}