CSP-S2019学习笔记:树的重心

2019年CSP提高组考了3题跟树有关的题:《括号树》、《树上的数》和《树的重心》。

《树的重心》这一题,题意比较好理解,通读两三遍题目后,再看一下样例1的解释,就能理解题意。

我在学习的过程中发到有人用到了向下倍增和两次深度优先搜索,在深度优先搜索的过程中还做了换根操作。这种解法真是非常精妙,让我受益匪浅。

因为之前做2018年NOIP提高组的《保卫王国》的时候,学过倍增,这次恰巧碰到这题再复习一下倍增,这样对倍增有了更加深刻的认识。倒是在寻找重心及换根的过程中,花了不少时间来学习。

这题的题目难度是省选,我用了三天的时间才学明白,不过比起另一道省选难度的题《划分》所用的五天时间,还是幸运了不少。

最后附上AC代码:

#include<bits/stdc++.h>
using namespace std;

const int maxN = 300005;
//const int depth = 2;
const int depth = 17;

int T, n;
int cnt;            
int head[maxN];    
int nodeCnt[maxN];  
int nodeCnt2[maxN];
int father[maxN];  
int father2[maxN];  
int heavySon[maxN]; 
int heavySon2[maxN];
int newHeavySon[maxN];
int down[maxN][18];
long long ans;

struct edge
{
    int to;
    int next;
}e[maxN*2];

void add(int x, int y)
{
    ++cnt;
    e[cnt].to = y;
    e[cnt].next = head[x];
    head[x] = cnt;
}

void dfs(int x, int fa)
{
    nodeCnt[x] = 1; 
    father[x] = fa;

    for(int i = head[x]; i; i = e[i].next)
    {
        int y = e[i].to;
        if(y == fa)
        {
            continue;
        }

        dfs(y, x);

        nodeCnt[x] += nodeCnt[y];

        if(nodeCnt[y] > nodeCnt[heavySon[x]])
        {
            heavySon2[x] = heavySon[x];  
            heavySon[x] = y;          
        }
        else if(nodeCnt[y] > nodeCnt[heavySon2[x]])
        {
            heavySon2[x] = y;
        }
    }

    down[x][0] = heavySon[x];
    for(int i = 1; i <= depth; i++)
    {
        down[x][i] = down[down[x][i-1]][i-1];
    }
}

//求质心之和
int getCentSum(int x, int _nodeCnt)
{
    int heavyChainSize = nodeCnt2[newHeavySon[x]]; 
    int otherSize = _nodeCnt - nodeCnt2[x]; 
    int maxSize = max(heavyChainSize, otherSize);
    bool flag = (maxSize <= _nodeCnt / 2);
    return x * flag;
}

void dfs2(int x, int fa)
{
    for(int i = head[x]; i; i = e[i].next) 
    {
        int y = e[i].to;
        if(y == fa)
        {
            continue;
        }

        nodeCnt2[x] = nodeCnt[1] - nodeCnt[y];//被分割后含x那一棵子树里有多少个节点
        father2[y] = 0; //为换根作准备
        father2[x] = 0;

        if(heavySon[x] == y)    //y是x的重子节点
        {
            //y是x的重子节点,因为x和y之间的边已经被删除了,所以x的第二重子节点变成了重子节点
            newHeavySon[x] = heavySon2[x];
        }
        else
        {
            //重子节点和父节点x仍然在同一棵树里,则重子节点仍为重子节点
            newHeavySon[x] = heavySon[x];
        }


        if(nodeCnt2[fa] > nodeCnt2[newHeavySon[x]])
        {//换根后,旧的父节点(已变成子节点)成了重子节点
            newHeavySon[x] = fa;
        }

        //删除边后重新预处理x节点的重链
        down[x][0] = newHeavySon[x];
        for(int j = 1; j <= depth; j++)
        {
            down[x][j] = down[down[x][j-1]][j-1];
        }

        int cent = x; //重心
        for(int j = depth; j >= 0; j--)
        {
            if(nodeCnt2[x] - nodeCnt2[down[cent][j]] <= nodeCnt2[x] / 2)
            {
                cent = down[cent][j]; //找分裂出的第一棵树的重心
            }
        }

        //若节点x不是重心,重心要么在x的重儿子子树里,要么在x的父节点上
        ans += getCentSum(newHeavySon[cent], nodeCnt2[x]) + getCentSum(cent, nodeCnt2[x]) + getCentSum(father2[cent], nodeCnt2[x]);

        cent = y;
        for(int j = depth; j >= 0; j--)
        {
            if(nodeCnt2[y] - nodeCnt2[down[cent][j]] <= nodeCnt2[y]/2)
            {
                cent = down[cent][j]; //找分裂出的第二棵树的重心
            }
        }

        ans += getCentSum(newHeavySon[cent], nodeCnt2[y]) + getCentSum(cent, nodeCnt2[y]) + getCentSum(father2[cent], nodeCnt2[y]);

        father2[x] = y; //y反过来变成x的父节点
        dfs2(y, x);
    }

    //复原操作
    newHeavySon[x] = down[x][0] = heavySon[x];
    father2[x] = father[x];
    for(int j = 1; j <= depth; j++)
    {
        down[x][j] = down[down[x][j-1]][j-1];
    }
    nodeCnt2[x] = nodeCnt[x];
}


int main()
{
#ifndef ONLINE_JUDGE
    freopen("centroid.in", "r", stdin);
    //freopen("centroid.out", "w", stdout);
#endif

    scanf("%d", &T);
    while(T--)
    {
        memset(head, 0, sizeof(head));
        memset(heavySon, 0, sizeof(heavySon));
        memset(father2, 0, sizeof(father2));
        memset(father, 0, sizeof(father));

        cnt = 0; //边的编号
        ans = 0;

        scanf("%d", &n);
        for(int i = 1; i < n; i++)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            add(x, y);
            add(y, x);
        }

        dfs(1, 0);

        memcpy(nodeCnt2, nodeCnt, sizeof(nodeCnt2));
        memcpy(newHeavySon, heavySon, sizeof(newHeavySon));
        memcpy(father2, father, sizeof(father2));

        dfs2(1, 0);

        cout << ans << endl;
    }

    return 0;
}

====================================
了解信息学奥赛请加微信307591841或QQ群581357582

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值