输入描述
本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。
输入格式
输入在第一行给出家族人口总数 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]+(ans−1)。初步估计一下复杂度应该是 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 < i n t , i n t > m p map<int,int>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;
}