• 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
    ",ans);
      }
      return 0;
    }
    
    
  • 相关阅读:
    Broadcom BCM94352z/DW1560驱动新姿势
    amd显卡更新最新驱动鼠标顿卡的解决方法
    设置 P2415Q & P2715Q 显示器使其支持 HDMI 2.0 启用 4k@60hz
    Web基础之Redis
    前端基础之AJAX
    Java基础之枚举
    解决Tomcat在idea控制台乱码问题
    JQuery基础
    JavaScript基础笔记
    前端基础之Html、CSS
  • 原文地址:https://www.cnblogs.com/NuoCarter/p/14448913.html
Copyright © 2020-2023  润新知