[关闭]
@xiaoziyao 2020-10-30T10:02:28.000000Z 字数 4476 阅读 1362

P5666 树的重心解题报告&关于树的重心的奇技淫巧

解题报告 杂项


虽然这道题已经写不了解题报告了,但是还是写一下题解纪念我一个下午+一个上午的努力吧。

P5666 树的重心

题意

分析

树上毒瘤题。

为了方便,我们设重心为根节点。

首先,给定一些关于树的重心的性质:

我们发现割断每一条边求两棵树的重心等价于求一棵子树的重心和整棵树去掉一棵子树的重心,那么下面进行分类讨论:

(注意,根据性质,我们可以找到深度较深的重心,然后判断它的父亲是否也为重心)

求一棵子树的重心

根据上面的性质,我们可以求出所有子树的重心。

具体的说,我们直接找到重儿子的重心,然后暴力向上跳直到这个点满足重心的定义。

简单说明一下复杂度为什么是正确的:很显然每一条边最多被一次暴力跳经过,因此复杂度为

  1. void dfs2(int x,int last){
  2. if(iptson[x]==-1){//是叶子节点
  3. treepos[x]=x;
  4. return ;
  5. }
  6. for(int i=start[x];i;i=then[i]){
  7. int y=to[i];
  8. if(y==last)
  9. continue;
  10. dfs2(y,x);
  11. }
  12. int now=treepos[iptson[x]];
  13. while(size[x]-size[now]>size[x]/2)
  14. now=fa[now];
  15. treepos[x]=now;
  16. }

求整棵树去掉一棵子树的重心

这个非常麻烦,我们需要继续分类讨论:

这颗子树不在根节点重儿子的子树中

因为这样的操作后重儿子仍然是重儿子,因此重心只会在根节点所在的重链上。进而有一个结论:只要删去的子树的大小相同,重心位置就相同。这样,我们就可以暴力预处理出来删除每个大小的子树的重心位置。

这棵子树在根节点重儿子的子树中

如果删除了这棵子树后重儿子仍然是重儿子,那么重心就是根节点(这个很显然,因为根节点是原来的重心)。

否则我们可以像上面一样进行预处理。

  1. int dfs3(int x,int p){//p=0表示预处理根节点重儿子,p=1表示预处理跟节点次重儿子
  2. int now;
  3. if(iptson[x]==-1)
  4. now=n-(p==0? size[iptson[pos]]:size[secson[pos]]);//删的再多也不能删到(次)重儿子所在的子树上
  5. else{
  6. if(x==pos&&p==1)
  7. now=dfs3(secson[x],1);
  8. else now=dfs3(iptson[x],p);
  9. }
  10. while(now>0&&n-now-size[x]<=(n-now)/2)
  11. except[now][p]=x,now--;
  12. return now;
  13. }

因为上面的循环只需要执行次,而的,因此这种预处理也是的。

然后是求答案的部分:

(注意,是否在根节点重儿子子树中,代表在)

  1. int solve(){
  2. int res=0;
  3. for(int i=1;i<n;i++){
  4. int x=xx[i],y=yy[i];
  5. if(dep[x]>dep[y])//保证$y$是$x$的儿子
  6. swp(x,y);
  7. int c1=treepos[y],c2=fa[c1],c3=0,c4=0;//c1,c2,c3,c4为四个重心位置(c1,c2在y子树里,c3,c4在y子树外)
  8. if(c2==0||size[iptson[c2]]>size[y]/2)//如果不存在c2或者c2的重儿子大小大于总大小的1/2,那么y子树中没有第二个重心
  9. c2=0;
  10. //下面讨论不在y子树中的重心
  11. if(flg[y]==0){//不在根节点重儿子所在的子树中
  12. c3=except[size[y]][0],c4=fa[c3];
  13. if(c4==0||size[iptson[c4]]>(n-size[y])/2)//如果不存在出或者c4的重儿子大小大于总大小的1/2,那么y子树外没有第二个重心
  14. c4=0;
  15. }
  16. if(flg[y]==1){//在根节点重儿子所在的子树中
  17. if(size[iptson[pos]]-size[y]>=size[secson[pos]])//不破坏重儿子,那么重心就是根节点
  18. c3=pos,c4=0;
  19. else{//破坏重儿子
  20. c3=except[size[y]][1],c4=fa[c3];
  21. int c4son=c4==pos? secson[c4]:iptson[c4];
  22. if(c4==0||size[c4son]>(n-size[y])/2)//如果不存在c4或者c4的某个儿子大小大于总大小的1/2,那么y子树外没有第二个重心(注意这里如果c4为根节点,那么因为重儿子大小小于次重儿子,所以需要取次重儿子大小)
  23. c4=0;
  24. }
  25. }
  26. res+=c1+c2+c3+c4;
  27. }
  28. return res;
  29. }

综上所述,总时间复杂度:

代码

  1. #include<stdio.h>
  2. #include<string.h>
  3. #define int long long
  4. #define inf 100000000000000000
  5. const int maxn=300005,maxm=600005;
  6. int T,n,e,minn,pos;
  7. int start[maxn],to[maxm],then[maxm],size[maxn],iptson[maxn],secson[maxn],flg[maxn],dep[maxn],treepos[maxn],fa[maxn],except[maxn][2],xx[maxn],yy[maxn];
  8. inline int max(int a,int b){
  9. return a>b? a:b;
  10. }
  11. inline void swp(int &a,int &b){
  12. a+=b,b=a-b,a-=b;
  13. }
  14. inline void add(int x,int y){
  15. then[++e]=start[x],start[x]=e,to[e]=y;
  16. }
  17. int getcenter(int x,int last){//找重心
  18. int sz=1,maxx=0;
  19. for(int i=start[x];i;i=then[i]){
  20. int y=to[i];
  21. if(y==last)
  22. continue;
  23. int res=getcenter(y,x);
  24. sz+=res,maxx=max(maxx,res);
  25. }
  26. maxx=max(maxx,n-sz);
  27. if(maxx<minn)
  28. minn=maxx,pos=x;
  29. return sz;
  30. }
  31. void dfs1(int x,int last){//预处理各种变量
  32. iptson[x]=secson[x]=-1,size[x]=1,dep[x]=dep[last]+1,fa[x]=last;
  33. for(int i=start[x];i;i=then[i]){
  34. int y=to[i];
  35. if(y==last)
  36. continue;
  37. dfs1(y,x);
  38. size[x]+=size[y];
  39. if(iptson[x]==-1||size[y]>size[iptson[x]])
  40. secson[x]=iptson[x],iptson[x]=y;
  41. else if(secson[x]==-1||size[y]>size[secson[x]])
  42. secson[x]=y;
  43. }
  44. }
  45. void dfs2(int x,int last){//求子树重心
  46. flg[x]=flg[last];
  47. if(x==iptson[pos])
  48. flg[x]=1;
  49. if(iptson[x]==-1){
  50. treepos[x]=x;
  51. return ;
  52. }
  53. for(int i=start[x];i;i=then[i]){
  54. int y=to[i];
  55. if(y==last)
  56. continue;
  57. dfs2(y,x);
  58. }
  59. int now=treepos[iptson[x]];
  60. while(size[x]-size[now]>size[x]/2)
  61. now=fa[now];
  62. treepos[x]=now;
  63. }
  64. int dfs3(int x,int p){//求删除某个大小的子树后重心的位置
  65. int now;
  66. if(iptson[x]==-1)
  67. now=n-(p==0? size[iptson[pos]]:size[secson[pos]]);
  68. else{
  69. if(x==pos&&p==1)
  70. now=dfs3(secson[x],1);
  71. else now=dfs3(iptson[x],p);
  72. }
  73. while(now>0&&n-now-size[x]<=(n-now)/2)
  74. except[now][p]=x,now--;
  75. return now;
  76. }
  77. int solve(){//求解答案
  78. int res=0;
  79. for(int i=1;i<n;i++){
  80. int x=xx[i],y=yy[i];
  81. if(dep[x]>dep[y])
  82. swp(x,y);
  83. int c1=treepos[y],c2=fa[c1],c3=0,c4=0;
  84. if(c2==0||size[iptson[c2]]>size[y]/2)
  85. c2=0;
  86. if(flg[y]==0){
  87. c3=except[size[y]][0],c4=fa[c3];
  88. if(c4==0||size[iptson[c4]]>(n-size[y])/2)
  89. c4=0;
  90. }
  91. if(flg[y]==1){
  92. if(size[iptson[pos]]-size[y]>=size[secson[pos]])
  93. c3=pos,c4=0;
  94. else{
  95. c3=except[size[y]][1],c4=fa[c3];
  96. int c4son=c4==pos? secson[c4]:iptson[c4];
  97. if(c4==0||size[c4son]>(n-size[y])/2)
  98. c4=0;
  99. }
  100. }
  101. res+=c1+c2+c3+c4;
  102. }
  103. return res;
  104. }
  105. signed main(){
  106. scanf("%lld",&T);
  107. while(T--){
  108. e=0;
  109. memset(start,0,sizeof(start));
  110. memset(except,0,sizeof(except));
  111. memset(fa,0,sizeof(fa));
  112. memset(flg,0,sizeof(flg));
  113. scanf("%lld",&n);
  114. for(int i=1;i<n;i++){
  115. int x,y;
  116. scanf("%lld%lld",&x,&y);
  117. xx[i]=x,yy[i]=y;
  118. add(x,y),add(y,x);
  119. }
  120. minn=inf,pos=0;
  121. getcenter(1,0);
  122. dfs1(pos,0);
  123. dfs2(pos,0);
  124. dfs3(pos,0);
  125. dfs3(pos,1);
  126. printf("%lld\n",solve());
  127. }
  128. return 0;
  129. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注