P5666 树的重心题解

P5666 树的重心:

M 分析暴力:

很容易打出一个 \(O(n)\)枚举边 再 \(O(n)\) 求重心 的\(O(n^2)\)的算法

期望得分:40 points

D 直接正解:

(其实分析链和完美二叉树可以与暴力一共拿到75分)

正解:整体复杂度\(O(n log n)\)

给出结论:

结论:一棵以 x 为根的树的重心,一定在 x 的重儿子所构成的集合中,而所有重儿子就构成了一条重链,

所以整个结论就是:一棵以 x 为根的树的重心,一定在 x 向下的重链上

因为整棵树是长这样的:(重链用绿色标出)

首先对于这条重链上的结点,它的子树珂以分为两类,

一个是它本身的儿子,一个是往父亲走的儿子,

第一类的子树size本身珂以处理出来

第二类的子树size 珂以用 size[root]-size[x]算出,

因为关注重心只用关注最大的子树,因为一类size是从 叶子->root 递增的,二类 size是从 叶子->root递减的

所以考虑倍增处理

设一个状态 \(f[x][t]\) 为 重链上的结点 x 向下走 \(2^t\) 步 珂以到达的结点

从大到小枚举 t 向下跳就好了,

考虑拥有两个重心的情况,首先这两个重心一定是相邻的(受到链和完美二叉树数据的启发得出的结论),所以只需要跑到最下面的重心判断他的fa就好了

然后对于每个结点的每一条出边跑一次\(O(logn)\)的倍增答案判定就好了

因为在代码中我们规定了一个根节点 1 ,那么我们在从上到下扫描出边的时候,每一条出边的终点其实就是它在这个有根树下的儿子,这个儿子如果删掉了它的父亲,它的重链是不会改变的所以可以直接倍增统计,

而对于它的父结点:

会分两种情况:

第一种断开了重链会改变,显然断开的是它的重儿子,那么断开后的重链显然是它的次重链。

第二种不变就可以直接统计了。

Code:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3e5+10,LOGN=20;
int n,siz[N],f[N][LOGN],son[N],fa[N];
ll ans;
int T,head[N],to[N<<1],Next[N<<1],tot;
inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-'){f=-1;}ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=getchar();}
  return x*f;
}
inline void add(int u,int v){
  to[++tot]=v,Next[tot]=head[u];
  head[u]=tot;
}
inline void get_son(int x) {for(int i=1;i<LOGN;++i) f[x][i]=f[f[x][i-1]][i-1];}
inline void calculate(int x){
  int u=x;for(int i=LOGN-1;i>=0;--i){if(f[u][i]&&siz[f[u][i]]*2>=siz[x]) u=f[u][i];}
  if(siz[u]*2==siz[x]) ans+=fa[u];//统计两个重心的情况
  ans+=u;
  return;
}
inline void dfs(int x,int ff) {
  fa[x]=ff;siz[x]=1;
  for(int i=head[x];i;i=Next[i]){
    int y=to[i];
    if(y!=ff){
      dfs(y,x),siz[x]+=siz[y];
      if(siz[y]>siz[son[x]]) son[x]=y;
    }
  }
  f[x][0]=son[x];get_son(x);
}
inline void getans(int x,int ff) {
  int x1=0,x2=0; siz[0]=0;
  for(int i=head[x];i;i=Next[i]){
    int v=to[i];
    if(siz[v]>=siz[x1]) x2=x1,x1=v;
    else if(siz[v]>=siz[x2]) x2=v;//找出重儿子和次重儿子
  }
  for(int i=head[x];i;i=Next[i]){
    int v=to[i];
    if(v!=ff){
      calculate(v);//直接统计出点
      f[x][0]=v==x1?x2:x1,get_son(x);
      //如果说断开的是重儿子,那么次重儿子一定就是新树的新重儿子
      //再重新更新重链结点
      siz[x]-=siz[v],siz[v]+=siz[x];//更新新树的size
      calculate(x);//统计 x 的答案
      fa[x]=v,getans(v,x);
      siz[v]-=siz[x],siz[x]+=siz[v];//还原答案
    }
  }
  f[x][0]=son[x];
  get_son(x),fa[x]=ff;
}
inline void myclear(){
	memset(siz,0,sizeof(siz));memset(son,0,sizeof(son));
	memset(f,0,sizeof(f));memset(fa,0,sizeof(fa));
	memset(head,0,sizeof(head));memset(to,0,sizeof(to));
	memset(Next,0,sizeof(Next));
}
int main() {
  T=read();
  while(T--){
    tot=0;
  	myclear();
    ans=0;n=read();
    for(int i=1,u,v;i<n;++i) {
      u=read(),v=read();
      add(u,v);
      add(v,u);
    }
    dfs(1,0),getans(1,0);
    printf("%lld\n",ans);
  }
  return 0;
}

posted @ 2021-02-25 19:44  NuoCarter  阅读(80)  评论(0编辑  收藏  举报