2020牛客NOIP赛前集训营-普及组第三场C牛半仙的妹子树

本文介绍了一道算法题目,涉及树的遍历和距离计算。题目要求在给定的一棵树中,计算特定范围内节点受到具有超能力节点影响的磁场强度。通过两次BFS和DFS遍历,分别计算节点到超能力节点的距离和,距离平方和以及叶超能力节点的距离和。最终通过状态转移公式求解答案。
摘要由CSDN通过智能技术生成

链接:https://ac.nowcoder.com/acm/contest/7608/C
来源:牛客网
 

牛半仙有 n​ 个妹子,她们所在的位置组成一棵树,相邻两个妹子的距离为 1​。
有 m​ 个妹子具有超能力,她们会影响到其他妹子。
离所有具有超能力的妹子的最短距离在 [l,r]间的妹子会受到影响,会具有旪超能力。
这些具有能力的妹子共同形成了一个磁场。对于一个位置,一个具有超能力的妹子为其增加的磁场强度为妹子到这个位置的距离的平方,一个具有旪超能力的妹子为其增加的磁场强度为妹子到这个位置的距离。
现在牛半仙想知道一个位置的磁场强度有多大。

因为牛半仙对妹子们特别关心,所以他有 k​ 个询问。

输入描述:

第一行 5个正整数,代表 n,m,k,l,r​ 。
第 2​ 行到第 n行每行 2​ 个正整数 ui​,vi​,代表树中存在边 (ui,vi)。
第 n+1​ 行有 m​ 个正整数,代表这些妹子有超能力。
第 n+2​ 行有 k​ 个正整数,代表询问的妹子。对于每个询问,输出一行,代表该询问的答案。

输出描述:

对于每个询问,输出一行,代表该询问的答案。

示例1

输入

11 2 4 2 2
1 2
1 3
2 4
2 5
3 6
3 7
5 8
5 9
9 10
9 11
3 9
1 6 9 11

输出

14
34
20
32

题目大意:树上某点到所有叶超能力点的距离和超能力节点的距离的和

思路:1.先用bfs求出各点到最近的超能力节点的距离并标记叶超能力节点

            2.用dfs1从根节点开始从上向下遍历求出到每个节点到叶超能力节点的距离和(sum0z数组)、到所有超能力节点的距离和(sum1数组)、到所有超能力节点距离的平方和(数组sum2)

            3.通过父节点到所有超能力节点距离的平方的状态转移到子节点到所有超能力点距离的平方的状态

void dfs2( int x,int fa )
{
    ans[x]=sum2[x]+sum0[x];
    for(int i=0;i<g[x].size();++i )
    {
        int v=g[x][i];
        if( v==fa )
            continue;
        ll tmp=sum1[x]-sum1[v]-cnt1[v];
        sum2[v]=sum2[x]-2*(sum1[v]+cnt1[v])+tmp*2+m;
        sum1[v]+=(tmp+n-cnt1[v]);
        sum0[v]+=(sum0[x]-sum0[v]-cnt2[v]+cnt2[1]-cnt2[v]);
        dfs2(v,x);
    }
}

以图中通过sum1[1]求sum1[2]为例进行讲解

/*dis数组为父节点1到各个超能力节点的距离

sum1[1]=dis[3]*dis[3]+dis[9]*dis[9]

sum1[2]=(dis[3]+1)*(dis[3]+1)+(dis[9]-1)*(dis[9]-1)=dis[3]*dis[3]+dis[9]*dis[9]+2*dis[3]-2*dis[9]+1+1;

            =sum1[1]+2*dis[3]-2*dis[9]+1+1;

现在推广到一般:dis[3]要理解成父节点到右子树所有超能力节点距离的平方,dis[9]要推广到父节点1到

子节点2其他所有超能力点的距离和,最后面的两个1分别表示父节点左子树所有的超能力的点和父节点右子树所有超能力的点的数量,所以加起来就是超能力点的总数

dfs1 的代码中tmp表示的就是dis[1],sum1[v]+cnt1[v]表示的就是dis[9](注意sum1[v]是下一行才更新,所以表示的是2节点到下方超能力结点距离的平方)

叶能力节点的距离更新=父节点到左子树所有叶超能力节点的距离和+子节点到下方叶能力节点的距离和+

子节点下方叶能力节点的个数(父节点要经过多少次起始点为父节点和子节点的边)

其实这个题目跟牛客上的 牛客练习赛55-树 的升级版

上题详解点击下方大佬博客

https://blog.csdn.net/qq_21433411/article/details/103534413

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
vector<int> g[maxn];
int sup[maxn],psup[maxn],vis[maxn];
int n,m,k,l,r;
int dis[maxn];
bool vsup[maxn],vpsup[maxn];
ll sum1[maxn],sum2[maxn]; // 超能力距离之和  超能力距离平方和
ll sum0[maxn]; // 旪超能力距离之和
ll cnt1[maxn],cnt2[maxn];  // 从第i个节点从上向下遍历得到的超能力节点和叶节点能力(包括自身)
ll ans[maxn];
void bfs()
{
    queue<int> que;
    for( int i=1; i<=m; i++ )
        vis[sup[i]]=1,vsup[sup[i]]=true,dis[sup[i]]=0,que.push(sup[i]);
    while( !que.empty() )
    {
        int p=que.front();
        que.pop();
        for( int i=0;i<g[p].size();++i )
        {
            int v=g[p][i];
            if( vis[v] )
                continue;
            dis[v]=dis[p]+1;///bfs是按距离从小到大扫的
            vis[v]=1;
            que.push(v);
        }
    }
    for( int i=1; i<=n; i++ )
        if( dis[i]>=l && dis[i]<=r )//第i个点到超能力点的最短距离
            vpsup[i]=true;
}
void dfs1( int x,int fa )
{
    for(int i=0;i<g[x].size();++i)
    {
        int v=g[x][i];
        if( v==fa )
            continue;
        dfs1(v,x);
        cnt1[x]+=cnt1[v];
        cnt2[x]+=cnt2[v];
        sum1[x]+=sum1[v]+cnt1[v];
        sum2[x]+=sum1[v]*2+sum2[v]+cnt1[v];
        sum0[x]+=sum0[v]+cnt2[v];
    }               ///dfs2前的sum0,sum1,sum2代表从第i个节点从上往下开始遍历
    if( vsup[x] )   ///到每个叶超能力节点的距离和到每个超能力节点距离和距离平方和
        cnt1[x]++;
    if( vpsup[x] )
        cnt2[x]++;
}
void dfs2( int x,int fa )
{
    ans[x]=sum2[x]+sum0[x];
    for(int i=0;i<g[x].size();++i )
    {
        int v=g[x][i];
        if( v==fa )
            continue;
        ll tmp=sum1[x]-sum1[v]-cnt1[v];
        sum2[v]=sum2[x]-2*(sum1[v]+cnt1[v])+tmp*2+m;
        sum1[v]+=(tmp+cnt1[1]-cnt1[v]);
        sum0[v]+=(sum0[x]-sum0[v]-cnt2[v]+cnt2[1]-cnt2[v]);
        dfs2(v,x);
    }
}
int main()
{
    scanf("%d%d%d%d%d",&n,&m,&k,&l,&r);
    for( int i=1; i<n; i++ )
    {
        int x,y;
        scanf("%d%d",&x,&y);
        g[x].push_back(y);
        g[y].push_back(x);
    }
    for( int i=1; i<=m; i++ )
        scanf("%d",&sup[i]);
    sort(sup+1,sup+1+m);
    m=unique(sup+1,sup+1+m)-(sup+1);
    bfs();
    dfs1(1,0);
    dfs2(1,0);
    while( k-- )
    {
        int x;
        scanf("%d",&x);
        printf("%lld\n",ans[x]);
    }
}

注:此代码借鉴了牛友

一命为红颜

的AC代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值