[关闭]
@xiaoziyao 2021-08-08T10:23:18.000000Z 字数 1923 阅读 862

CF566C Logistical Questions 解题报告

解题报告


CF566C Logistical Questions解题报告:

更好的阅读体验

题意

给定一颗 个点,带点权和边权的树,设一条路径的权值为边权的 次方,求树的带权重心。

分析

大清新题。

我们发现真正的带权重心不一定在结点上(虽然答案在结点上),可能在一条边的某个位置,设其为 。如果重心恰好在一个结点上,我们钦定它在这个点相连的一条边无限接近端点的一个位置。

先考虑树只有两个点的情况,设唯一的一条边为 ,那么如果当前在边上与 的距离为 的位置,那么权值和为:

我们对其求导,得到

带入边的两个端点 得到

于是我们发现点在边上移动时,靠近带权重心时权值和减小,远离带权重心是权值和增加,也就是这个函数是严格下凸的。

我们将情况扩展到树上,在一条边上运动时,新的距离和 可以看做若干个 的和,于是它仍然是严格下凸的。

这是一个非常好的性质,它告诉我们如果一个点靠近重心,权值和一定减小,否则一定增加。

我们从任意一个点开始,每次通过它子树中所有点来计算每一条边的导数,取负数的那一条边。这样做的复杂度是 的。

但是我们发现可以在点分治中做这样的操作,具体就每次走一条边后就直接跳到这个子树重心,这样复杂度就是 了。

代码

  1. #include<stdio.h>
  2. #include<math.h>
  3. #define inf 1000000000
  4. const int maxn=200005,maxm=maxn<<1;
  5. int n,e,stp,minn,pos,ans;
  6. int v[maxn],start[maxn],to[maxm],then[maxm],worth[maxm],vis[maxn],size[maxn],fin[maxn];
  7. double val,all,now;
  8. double tot[maxn];
  9. inline int max(int a,int b){
  10. return a>b? a:b;
  11. }
  12. inline void add(int x,int y,int z){
  13. then[++e]=start[x],start[x]=e,to[e]=y,worth[e]=z;
  14. }
  15. void find(int x,int sum){
  16. int maxx=0;
  17. vis[x]=stp,size[x]=1;
  18. for(int i=start[x];i;i=then[i])
  19. if(fin[to[i]]==0&&vis[to[i]]!=stp)
  20. find(to[i],sum),size[x]+=size[to[i]],maxx=max(maxx,size[to[i]]);
  21. maxx=max(maxx,sum-size[x]);
  22. if(maxx<minn)
  23. minn=maxx,pos=x;
  24. }
  25. void dfs(int x,int last,int dis,int top){
  26. now+=1.0*v[x]*dis*sqrt(dis),all+=1.5*v[x]*sqrt(dis),tot[top]+=1.5*v[x]*sqrt(dis);
  27. for(int i=start[x];i;i=then[i])
  28. if(to[i]!=last)
  29. dfs(to[i],x,dis+worth[i],top);
  30. }
  31. void solve(int x,int sum){
  32. stp++,minn=inf,find(x,sum),x=pos,fin[x]=1;
  33. all=now=0;
  34. for(int i=start[x];i;i=then[i])
  35. tot[to[i]]=0,dfs(to[i],x,worth[i],to[i]);
  36. if(now<val)
  37. val=now,ans=x;
  38. find(x,sum);
  39. for(int i=start[x];i;i=then[i])
  40. if(fin[to[i]]==0&&all-2*tot[to[i]]<0){
  41. solve(to[i],size[to[i]]);
  42. break;
  43. }
  44. }
  45. int main(){
  46. scanf("%d",&n);
  47. for(int i=1;i<=n;i++)
  48. scanf("%d",&v[i]);
  49. for(int i=1;i<n;i++){
  50. int x,y,z;
  51. scanf("%d%d%d",&x,&y,&z);
  52. add(x,y,z),add(y,x,z);
  53. }
  54. val=1e30,solve(1,n);
  55. printf("%d %.10lf\n",ans,val);
  56. return 0;
  57. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注