PTA L2-026 小字辈(并查集)

输入描述

本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。

输入格式

输入在第一行给出家族人口总数 N N N(不超过 100 000 的正整数) —— 简单起见,我们把家族成员从 1 1 1 N N N 编号。随后第二行给出 N N N 个编号,其中第 i i i 个编号对应第 i i i 位成员的父/母。家谱中辈分最高的老祖宗对应的父/母编号为 − 1 -1 1。一行中的数字间以空格分隔。

输出格式

首先输出最小的辈分(老祖宗的辈分为 1 1 1,以下逐级递增)。然后在第二行按递增顺序输出辈分最小的成员的编号。编号间以一个空格分隔,行首尾不得有多余空格。

输入样例
9 9 9
2 2 2 6 6 6 5 5 5 5 5 5 − - 1 1 1 5 5 5 6 6 6 4 4 4 7 7 7
输出样例
4 4 4
1 1 1 9 9 9
思路

一看到题目描述中亲戚,家谱之类的关键词,就想到了并查集,但这道题用dfs搜索的话应该也是可以,但是没有去尝试过。用并查集写完代码之后才发现这跟暴力完全就没有什么区别,意料之中,提交 T T T了。而且这道题不能用我之前了解到的路径压缩方法,看了别人博客之后才了解到,对于每一个节点 A A A,我可以不用遍历直到根节点计数器ans++来计算它的辈分,只要计算到某个节点 B B B,其辈分数量不为 0 0 0,那么 [ A ] = [ B ] + a n s [A]=[B]+ans [A]=[B]+ans,并且如果A的父亲节点 C C C此时的辈分为 0 0 0,那么 [ C ] = [ A ] + ( a n s − 1 ) [C]=[A]+(ans-1) [C]=[A]+(ans1)。初步估计一下复杂度应该是 O ( n ) O(n) O(n)

超时代码
#include <iostream>
#include <cstring>
#include <cstdio>
#define INF 0x3f3f3f
using namespace std;
const int mod=1e9+7;
const int Max_N=1e5+10;
typedef pair<int,int>P;
typedef long long ll;
typedef unsigned long long ull;
int main(int argc, char const *argv[])
{
    int n;
    cin>>n;
    int father[Max_N];
    memset(father,-1,sizeof(father));
    for(int i=1;i<=n;i++)
    {
        cin>>father[i];
    }
    int path[Max_N];
    memset(path,0,sizeof(path));
    int maxnum=0;
    for(int i=1;i<=n;i++)
    {
        int mid=i;
        while(father[mid]!=-1)
        {
            mid=father[mid];
            path[i]++;
        }
        maxnum=max(maxnum,path[i]);
    }
    printf("%d\n",maxnum+1);
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(path[i]==maxnum)
        {
            if(!cnt)
            {
                cnt=1;printf("%d",i);
            }
            else printf(" %d",i);
        }
    }
    return 0;
}
奇葩现象

在优化思路之后发现自己写的代码输入测试样例的时候一直崩,百思不得其解,随便提交一发,居然过了(别问我为什么样例没过,哪来的勇气提交,其实我也不知道),最后发现题目要求家谱中辈分最高的老祖宗对应的父/母编号为 -1,数组的下标是不能为负的,对于第一个节点,遍历结束是肯定要到辈分最高的老祖宗。(到此,我也还没明白为什么提交能过)。

过不了样例,但能AC的代码
#include <bits/stdc++.h>
#define INF 0x3f3f3f
using namespace std;
const int mod=1e9+7;
const int Max_N=1e5+10;
typedef pair<int,int>P;
typedef long long ll;
typedef unsigned long long ull;
int pre[Max_N];
int path[Max_N];
int res=0;
void init()
{
    for(int i=0;i<=Max_N;i++)
    {
        pre[i]=-1;
    }
}
void find(int x)
{
    int r=x;
    int ans=0;
    while(pre[r]!=-1&&path[r]==0)
    {
        r=pre[r];
        ans++;
    }
    int tem=x;
    while(path[tem]==0)
    {
        path[tem]=ans+path[r];
        res=max(res,path[tem]);
        ans--;
        tem=pre[tem];
    }
}
int main(int argc, char const *argv[])
{
    int n;
    init();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>pre[i];
    }
    for(int i=1;i<=n;i++)
    {
        find(i);
    }
    cout<<res+1<<endl;
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(path[i]==res)
        {
            if(!ans)
            {
                cout<<i;ans=1;
            }
            else cout<<" "<<i;
        }
    }
    cout<<endl;
    return 0;
}
解决方案

在我发现问题之后,我认为有两种解决方案

  • m a p map map结构来代替数组(声明 m a p &lt; i n t , i n t &gt; m p map&lt;int,int&gt;mp map<int,int>mp m a p map map结构可以类似于数组用 m p [ i ] mp[i] mp[i]的形式来访问数据)
  • f i n d find find函数中的第二个 w h i l e while while里再多加一个判断。

我选择了第二种方案,但是这个判断也要加的恰到好处,若写成

while(path[tem]==0&&tem!=-1)

则依然会出现上文中提到过的现象,那到底怎么写呢?
个人认为应该写成这样

while(tem!=-1&&path[tem]==0)

在这儿就用到了&&的短路性质,当 t e m tem tem − 1 -1 1时, t e m ! = − 1 tem!=-1 tem!=1的结果为 f a l s e false false,那么就会出现直接结束 w h i l e while while循环的现象,就不会再去判断 p a t h [ t e m ] = = 0 path[tem]==0 path[tem]==0是否成立。问题就迎刃而解了。

AC代码
#include <bits/stdc++.h>
#define INF 0x3f3f3f
using namespace std;
const int mod=1e9+7;
const int Max_N=1e5+10;
typedef pair<int,int>P;
typedef long long ll;
typedef unsigned long long ull;
int pre[Max_N];
int path[Max_N];
int res=0;
void init()
{
    for(int i=0;i<=Max_N;i++)
    {
        pre[i]=-1;
    }
}
void find(int x)
{
    int r=x;
    int ans=0;
    while(pre[r]!=-1&&path[r]==0)
    {
        r=pre[r];
        ans++;
    }
    int tem=x;
    while(tem!=-1&&path[tem]==0)
    {
        path[tem]=ans+path[r];
        res=max(res,path[tem]);
        ans--;
        tem=pre[tem];
    }
}
int main(int argc, char const *argv[])
{
    int n;
    init();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>pre[i];
    }
    for(int i=1;i<=n;i++)
    {
        find(i);
    }
    cout<<res+1<<endl;
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(path[i]==res)
        {
            if(!ans)
            {
                cout<<i;ans=1;
            }
            else cout<<" "<<i;
        }
    }
    cout<<endl;
    return 0;
}
不足之处还望多多指教
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值