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

本文探讨了一种使用并查集和深度优先搜索(DFS)算法解决寻找庞大家族中最小辈分成员的问题。通过对家谱的遍历,优化后的代码能够在O(n)的时间复杂度内找到所有最小辈分的成员。文章详细分析了代码实现过程中的常见错误与解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

输入描述

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

输入格式

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

输出格式

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

输入样例
999
222 666 555 555 −-111 555 666 444 777
输出样例
444
111 999
思路

一看到题目描述中亲戚,家谱之类的关键词,就想到了并查集,但这道题用dfs搜索的话应该也是可以,但是没有去尝试过。用并查集写完代码之后才发现这跟暴力完全就没有什么区别,意料之中,提交TTT了。而且这道题不能用我之前了解到的路径压缩方法,看了别人博客之后才了解到,对于每一个节点AAA,我可以不用遍历直到根节点计数器ans++来计算它的辈分,只要计算到某个节点BBB,其辈分数量不为000,那么[A]=[B]+ans[A]=[B]+ans[A]=[B]+ans,并且如果A的父亲节点CCC此时的辈分为000,那么[C]=[A]+(ans−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;
}
解决方案

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

  • mapmapmap结构来代替数组(声明map&lt;int,int&gt;mpmap&lt;int,int&gt;mpmap<int,int>mpmapmapmap结构可以类似于数组用mp[i]mp[i]mp[i]的形式来访问数据)
  • findfindfind函数中的第二个whilewhilewhile里再多加一个判断。

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

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

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

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

在这儿就用到了&&的短路性质,当temtemtem−1-11时,tem!=−1tem!=-1tem!=1的结果为falsefalsefalse,那么就会出现直接结束whilewhilewhile循环的现象,就不会再去判断path[tem]==0path[tem]==0path[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;
}
不足之处还望多多指教
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值