zoukankan      html  css  js  c++  java
  • 浅谈树的重心

    浅谈树的直径


    定义:

      树上一节点最大子树的节点数最小;

    性质:

      1.删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心;

      2.树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等;

      3.两个树通过一条边合并,新的重心在原树两个重心的路径上;

      4.树删除或添加一个叶子节点,重心最多只移动一条边;

    求解:

      求解方法多种多样,分别用到不同的定义和性质:

      1.定义求解:

      siz [ i ]表示 i 节点的子树大小 dp [ i ]表示以 i 为根节点的最大子树大小,val[ i ]为i节点的点权,代码通俗易懂不过多解释了

      

    inline void dfs(int now,int fa)
    {
        siz[now]=val[now];
        dp[now]=0;
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(t==fa) continue;
            dfs(t,now);
            siz[now]+=siz[t];
            dp[now]=max(dp[now],siz[t]);
        }
        dp[now]=max(dp[now],n-dp[now]);
        if(dp[now]<dp[ans]) ans=now;
    }

      2.性质求解:

      一般来说定义求解就够用了,但在某些时候性质求解更方便实用;

      根据性质2:我们可以处理出所有节点到某一节点的距离,取最小值;

      怎么求出每个节点到某一节点的距离呢?在dfs过程中向下处理是很容易的,所以我们可以先处理出所有节点到根节点的距离qwq ;

      siz [ i ]同上,f [ i ] 表示节点 i 的所有子节点到 i 的距离和,val[ i ] 同上, a [ i ].val 为边权,设定1 号节点为根节点;

      

    inline void dfs1(int now,int fa,int deep)
    {
        siz[now]=val[now];
        dep[now]=deep;
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(t==fa) continue;
            dfs1(t,now,deep+a[i].val);
            siz[now]+=siz[t];
            f[now]+=f[t]+siz[t]*a[i].val;
        }
    }

      对于f数组的处理的理解:t的所有子节点到t的距离+now的当前子树所有节点到now的距离。

      这样就求得了根节点的距离和,我们再通过根节点递推其他节点的距离和,有如下公示:

      f [ now ] = f [ fa ] +( siz [ 根节点 ] - 2 * siz [ now ])* 边权;(now!= 根节点)

      理解如下:

      对于now的子节点,每个节点的距离减少了一个边权,总距离减少 siz [ now ] * 边权 ,对于非v子节点,每个节点距离增加了一个边权,总距离增加(siz[ 根 ]-siz [ now ])*边权

      

    inline void dfs2(int now,int fa)
    {
        if(now^root) f[now]=f[fa]+siz[1]-2*siz[now];
        if(f[now]<sum) res=now,sum=f[now];
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(t==fa) continue;
            dfs2(t,now); 
        }
    }

      这种方法还可以优化:观察式子:显然一个节点的所有子树中,只有子节点数最多的一个可能成为重心,所以我们可以加以改进,在dfs2中只走子树节点最多的一个:

      这样复杂度整体虽然还是O(n)的,但是查询复杂度变为了O(树高)在某些题目中(下面例题中qwq)有奇效。

     

    inline void dfs1(int now,int fa,int deep)
    {
        siz[now]=val[now];
        dep[now]=deep;
        int maxson=-1;//新 加 
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(t==fa) continue;
            dfs1(t,now,deep+a[i].val);
            siz[now]+=siz[t];
            f[now]+=f[t]+siz[t]*a[i].val;
            if(siz[t]>maxson) maxson=siz[t],son[now]=t;//新 加 
        }
    }
    inline void dfs2(int now,int fa)
    {
        if(now^1) f[now]=f[fa]+siz[1]-2*siz[now];
        if(f[now]<sum) res=now,sum=f[now];
        if(son[now]) dfs2(t,now);//改 动 
    }

    例题: 

      洛谷P2726

      

      第一行为N,1<N<=50000,表示树的节点数目,树的节点从1到N编号。 接下来N-1行,每行两个整数U,V,表示U与V之间有一条边。 再接下N行,每行一个正整数,其中第i行的正整数表示编号为i的节点权值为W(I),树的深度<=100

      分析:

      应该没有黑题难度,紫色差不多。

      先考虑暴力枚举x,y,那么对于每一对x,y分界都是一条树上的边。那么我们不如枚举断边,再找出重心qwq;

      先O(n)求出f [ root ] 的值,枚举断边,再通过上述第二种优化过的方法求距离和,总复杂度O(N*树高);

      对于优化的处理:由于需要断边,每次断边后最大子树可能变小,所以我们需要维护一个次大子树;

      

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    inline int read()
    {
        int x=0,f=1;
        char ch;
        for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
        if(ch=='-') f=0,ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
        return f?x:-x;
    }
    const int inf=1e18;
    int n,res,cut;
    int val[100010],siz[100010],f[100010],dep[100010];
    int from[100010];
    int son1[100010],son2[100010];
    int head[100010],cnt=1;
    struct point
    {
        int nxt,to;
    }a[100010];
    inline void add(int x,int y)
    {
        a[++cnt].nxt=head[x];
        a[cnt].to=y;
        head[x]=cnt;
    }
    inline void dfs1(int now,int fa,int deep)
    {
        siz[now]=val[now];
        dep[now]=deep;
        from[now]=fa;
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(t==fa) continue;
            dfs1(t,now,deep+1);
            siz[now]+=siz[t];
            f[now]+=(f[t]+siz[t]);
            if(siz[t]>siz[son1[now]])
            {
                son2[now]=son1[now];
                son1[now]=t;
            }
            else if(siz[t]>siz[son2[now]])
            {
                son2[now]=t;
            }
        }
    }
    inline void dfs3(int now,int sum,int all,int &ans)
    {
        ans=min(ans,sum);
        int t=son1[now];
        if(t==cut||siz[son2[now]]>siz[son1[now]]) t=son2[now];
        if(!t) return ;    
        if(2*siz[t]>all) dfs3(t,sum+all-2*siz[t],all,ans);
    }
    inline void dfs2(int now)
    {
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(t==from[now]) continue;
            cut=t;
            for(int x=now;x;x=from[x]) siz[x]-=siz[t];
            int A=inf,B=inf;
            dfs3(1,f[1]-f[t]-dep[t]*siz[t],siz[1],A);
            dfs3(t,f[t],siz[t],B);
            res=min(res,A+B);
            for(int x=now;x;x=from[x]) siz[x]+=siz[t];
            dfs2(t);
        }
    }
    signed main()
    {
        n=read();
        for(int x,y,i=1;i<n;++i)
        {
            x=read(),y=read();
            add(x,y);
            add(y,x);
        }
        for(int i=1;i<=n;++i)
        {
            val[i]=read();
        }
        res=inf;
        dfs1(1,0,0);
        dfs2(1);
        printf("%lld
    ",res);
    return 0;
    }

      

  • 相关阅读:
    传输对象模式
    服务定位器模式
    拦截过滤器模式
    前端控制器模式
    数据访问对象模式
    组合实体模式
    业务代表模式
    MVC 模式
    访问者模式
    模板模式
  • 原文地址:https://www.cnblogs.com/knife-rose/p/11258403.html
Copyright © 2011-2022 走看看