@xiaoziyao
2021-08-08T18:23:18.000000Z
字数 1923
阅读 1052
解题报告
CF566C Logistical Questions解题报告:
给定一颗 个点,带点权和边权的树,设一条路径的权值为边权的 次方,求树的带权重心。
大清新题。
我们发现真正的带权重心不一定在结点上(虽然答案在结点上),可能在一条边的某个位置,设其为 。如果重心恰好在一个结点上,我们钦定它在这个点相连的一条边无限接近端点的一个位置。
先考虑树只有两个点的情况,设唯一的一条边为 ,那么如果当前在边上与 的距离为 的位置,那么权值和为:
我们对其求导,得到 。
带入边的两个端点 和 得到 ,。
于是我们发现点在边上移动时,靠近带权重心时权值和减小,远离带权重心是权值和增加,也就是这个函数是严格下凸的。
我们将情况扩展到树上,在一条边上运动时,新的距离和 可以看做若干个 的和,于是它仍然是严格下凸的。
这是一个非常好的性质,它告诉我们如果一个点靠近重心,权值和一定减小,否则一定增加。
我们从任意一个点开始,每次通过它子树中所有点来计算每一条边的导数,取负数的那一条边。这样做的复杂度是 的。
但是我们发现可以在点分治中做这样的操作,具体就每次走一条边后就直接跳到这个子树重心,这样复杂度就是 了。
#include<stdio.h>
#include<math.h>
#define inf 1000000000
const int maxn=200005,maxm=maxn<<1;
int n,e,stp,minn,pos,ans;
int v[maxn],start[maxn],to[maxm],then[maxm],worth[maxm],vis[maxn],size[maxn],fin[maxn];
double val,all,now;
double tot[maxn];
inline int max(int a,int b){
return a>b? a:b;
}
inline void add(int x,int y,int z){
then[++e]=start[x],start[x]=e,to[e]=y,worth[e]=z;
}
void find(int x,int sum){
int maxx=0;
vis[x]=stp,size[x]=1;
for(int i=start[x];i;i=then[i])
if(fin[to[i]]==0&&vis[to[i]]!=stp)
find(to[i],sum),size[x]+=size[to[i]],maxx=max(maxx,size[to[i]]);
maxx=max(maxx,sum-size[x]);
if(maxx<minn)
minn=maxx,pos=x;
}
void dfs(int x,int last,int dis,int top){
now+=1.0*v[x]*dis*sqrt(dis),all+=1.5*v[x]*sqrt(dis),tot[top]+=1.5*v[x]*sqrt(dis);
for(int i=start[x];i;i=then[i])
if(to[i]!=last)
dfs(to[i],x,dis+worth[i],top);
}
void solve(int x,int sum){
stp++,minn=inf,find(x,sum),x=pos,fin[x]=1;
all=now=0;
for(int i=start[x];i;i=then[i])
tot[to[i]]=0,dfs(to[i],x,worth[i],to[i]);
if(now<val)
val=now,ans=x;
find(x,sum);
for(int i=start[x];i;i=then[i])
if(fin[to[i]]==0&&all-2*tot[to[i]]<0){
solve(to[i],size[to[i]]);
break;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&v[i]);
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
}
val=1e30,solve(1,n);
printf("%d %.10lf\n",ans,val);
return 0;
}