2021中国大学生程序设计竞赛(CCPC)- 网络选拔赛(重赛)1011 Jumping Monkey

1011 Jumping Monkey

思路:
首先看当前图中剩下的权值最大的点u,要使得答案最优从任何一个节点开始跳,跳到最后肯定会跳到u。那么考虑把u从图中去掉,图会变成若干个连通块,这些连通块自己内部求最优解再加上跳到权值最大的点u就是正解。按照这个思路,每趟将所有连通块中权值最大的点拿出来,对于节点v,如果该节点在第k趟被拿出来,那么节点v的解为k
这里我们反向进行如上过程,对于最后一趟拿出来的点一定是每个连通块中最小的节点,我们按照权值从小到大遍历每个节点v,并将已经遍历过的并且和v联通的节点标记值w加1,每个节点初始标记值w为1,对节点u来说标记值w的含义为每趟将所有连通块中最大的点拿出来,节点u会在第w次拿出来。也就是反向进行了开始讲的过程。如下图:

在这里插入图片描述
复杂度分析:

  1. 对于节点从小到大按照权值排序O(nlogn)
  2. 循环遍历每个节点i,对于和节点i联通的所有节点标记值w+1 O(n^2)
    题目给的n的范围为1e5明显是复杂度太大了,下面我们优化一下

优化

//先给出find函数
int find(int x)
{
    if(x==fa[x])
    return x;
    else
    {
        return fa[x]=find(fa[x]);
    }
}

按照权值排序排完,先构造一个空的连通图,我们按照从小到大的顺序不断向连通图加入节点v,当前连通图中的连通块如果在原图中有一条边能将连通块和v相连,那么就将v和这个连通块权值最大的点相连,遍历完每个点之后从权值最大的那个点开始跑bfs求每个节点的深度,每个点的深度就是对应的答案,其中根节点的深度为1.正常搜索的话还是n^2的复杂度,这里可以用并查集存来存储每个连通块当前权值最大的点,对于每个点v,在原图中v一步能到达的所有节点求出来,然后看看这些节点在不在新建的连通图中,在的话就并查集求这个节点对应的连通块中权值最大的点,然后添加一条v指向该连通块中权值最大的点的边,并维护并查集。让我们模拟一下,首先排序排完之后,我们加入第一个点,让第一个节点独自构成一个连通块;加入第二个点,假设第二个点和第一个点相连,那么先查询第一个节点所在连通块的权值最大的点,也就是tx=find(第一个节点),看看tx是不是和第二个节点相同,不相同就把tx和第二个节点连一条边(第二个节点为起点,tx为终点,下面也都是这样,新加入的节点为起点,相连连通块中最大的点为终点),并fa[tx]=第二个节点,现在没有其他点和第二个点相连了;加入第三个点,假设第一个点和第二个点都和第三个点相连,tx1=find(第一个节点),看看是否tx1=第三个节点,不相等的话tx1与第三个节点连一条边,fa[tx1]=第三个节点,tx2=find(第二个节点),看看是否tx2=第三个节点,不相等的话tx2与第三个节点连一条边,并且fa[tx2]=第三个节点。接下来的节点按照这个策略遍历完。最后求结果,以权值最大的节点为根节点,并且根节点深度为1,求新构造的图的深度,对应的深度就是每个节点的解。

在这里插入图片描述

复杂度分析
图中n个节点n-1条边,对于每个节点将它相邻的节点拿出来,最多有2n个节点,因为所有节点度数总和为2n,也就是最多有2*n次并查集的查询,路径压缩并查集查询复杂度为O(Mlog1+M/NN)其中M为查询次数,N为节点数,所以优化后复杂度为O(nlogn)

代码

#include<bits/stdc++.h>
using namespace std;
int T,n;//节点数n
int head[100005],net[300005],v[300005],d[100005],w[100006],vis[100005];//原图,其中w为权值,d为深度
int tot=0,tot2;//原图边的数目tot1,新的连通图变得个数tot2
int fa[100005];
int head2[100005],net2[300005],v2[300005];//新的连通图
void add(int x,int y)//构造原图
{
    v[++tot]=y,net[tot]=head[x],head[x]=tot;
}
void add2(int x,int y)//构造新的连通图
{
    v2[++tot2]=y,net2[tot2]=head2[x],head2[x]=tot2;
}
void init()//初始化
{
    tot=0;
    tot2=0;
    for(int i=1;i<=n;i++)
    {
        d[i]=0,head[i]=0,vis[i]=0,head2[i]=0,fa[i]=i,vis[i]=0;
    }
}
int find(int x)//并查集查询节点x对应连通块中权值最大的点
{
    if(x==fa[x])
    return x;
    else
    {
        return fa[x]=find(fa[x]);
    }
}

struct node
{
    int p,w;//p为节点编号,w为节点权值
    const bool operator<(node y)const//按照权值从小到大排序
    {
        return this->w<y.w;
    }
}que[100005];

void bfs(int x)
{
    vis[x]=1;
    for(int i=head[x];i;i=net[i])//在原图中找到x点一步能到达的点
    {
        int y=v[i];
        if(!vis[y])//这个点不在新构造的连通图中
        continue;
        int ty=find(y);//查询这个节点对应连通块中权值最大的点

        if(ty==x)//如果这个连通块中权值最大的点不是x就令fa[ty]=x,并且在x和ty之间添加有向边e(x->ty)
        continue;
        add2(x,ty);
        fa[ty]=x;
    }
}
void bfs2(int x)//以x节点为根节点在新构造的连通图中求每个节点的深度
{
    queue<int>q;
    q.push(x);
    d[x]=1;//根节点深度为1
    while(q.size())
    {
        x=q.front();
        q.pop();
        for(int i=head2[x];i;i=net2[i])
        {
            int y=v2[i];
            if(d[y])
            continue;
            d[y]=d[x]+1;
            q.push(y);
        }
    }
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        init();
        for(int i=1;i<n;i++)//输入原图
        {
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        for(int i=1;i<=n;i++)//输入每个点的权值
        {
            scanf("%d",&w[i]);
            que[i].p=i;
            que[i].w=w[i];
        }
        sort(que+1,que+1+n);//对每个点按照权值大小从小到大排序

        for(int i=1;i<=n;i++)//排序后从小到大将每个节点加入新构造的连通图
        {
            bfs(que[i].p);
        }

        bfs2(que[n].p);//以权值最大的节点为根节点求每个节点的深度
        for(int i=1;i<=n;i++)//输出每个节点的深度,也就是解
        printf("%d\n",d[i]);
    }
    return 0;
}
  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值