当前位置:首页 > 题解目录 > 正文内容

【题解】寻找祖先

亿万年的星光2年前 (2023-04-21)题解目录1823

【题目描述】

给出充足的父子关系,请你编写程序找到某个人的最早的祖先。
规定每个人的名字都没有空格,且没有任意两个人的名字相同。最多可能有1000组父子关系,总人数最多可能达到50000人,家谱中的记载不超过30代。

【输入描述】

输入由多行组成,首先是一系列有关父子关系的描述,其中每一组父子关系中父亲只有一行,儿子可能有若干行;用#name的形式描写一组父子关系中的父亲的名字,用+name的形式描写一组父子关系中的儿子的名字,儿子的名字一定紧接着父亲的名字出现;
接下来用?name的形式表示要求该人的最早的祖先;(?name一定在父子关系描述结束之后出现)
最后用单独的一个$表示文件结束。

【输出描述】

按照输入文件的要求顺序,求出每一个要找祖先的人的祖先,格式:本人的名字+一个空格+祖先的名字+回车。

【样例输入】

#George
+Rodney
#Arthur
+Gareth
+Walter
#Gareth
+Edward
?Edward
?Walter
?Rodney
?Arthur
$

【样例输出】


Edward Arthur
Walter Arthur
Rodney George
Arthur Arthur


【题目分析】

  • 首先,考点是并查集,问题的难点在于处理字符串问题,原本的并查集用的数字和数字之间的对应关系,在这个题目中变成了字符串和字符串之间的对应关系了。

  • 原先并查集的指向关系是 f[2]=1,现在的并查集指向关系类似 f[tom]=jerry

  • 当前的儿子有可能是别人的爸爸,当前的爸爸有可能是别人的儿子,所以,首先看看当前的这个人有没有出现过,如果没有就初始化



【参考答案一:结构体】

我们建立起结构体,用结构体的下标和结构体的属性进行关联,假设结构体定义如下:

struct people {
	string name; //存储人的名字
	int num;	//存储人的编号,用来关联名字
};

根据测试样例,我们对数据初始化应该如下:


注意:因为有上面说的“父亲和儿子有可能出现过”,所以有的名称不会两次初始化:

我们前面学过并查集的基本模板,父子之间的指向关系用 p[2]=5这种类型,在上面这种结构体中,我们用 p[i].num=p[j].num这种类型。

有一个关系从来没有变化过,也就是下标(左侧小圆圈里的数)和name的对应关系,也就是下标就是结构体中的名字属性,而结构体的num属性就是父节点。

比如,i=4, 根据并查集的知识,它的num应该是2,因为它的父节点是2。

那么,我们可以快速手动模拟出结论如下:


也就是说,我们在最后查询的时候,根据人的名字,找到对应的编号(num),这个num,就是他祖先的下标,根据这个下标就可以找到祖先。


另外,第二点需要注意的是,我们前面学过 find函数和merge函数的优化,但是在这里需要注意,不能使用merge函数的优化,因为题目中的父子关系已经给定,不能根据秩的大小决定谁是父亲,谁是儿子。

其次,我们前面学过merge函数,我们说过,p[x]=y 和p[y]=x 对于求解关系来说都一样,不影响结果,只要保证,每次的关系都是一样即可,但是在本题中,不能这么想,本题的父子关系非常明确,一定是

p[儿子]=父亲 这种格式



【答案】

#include<bits/stdc++.h>
using namespace std;

int index=0; //用来表示结构体的数量
//定义结构体,用来存储人
struct people {
	string name; //存储人的名字
	int num;	//存储人的编号,用来关联名字
};
people p[500001];
//查找函数
int find(int n) {
	if(p[n].num==n)
		return p[n].num;
	else
		return p[n].num=find(p[n].num);

}
//合并操作
void merge(int a,int b) {
	int fa=find(a);
	int fb=find(b);
	if(fa==fb) return ;
	else 
		p[fb].num=p[fa].num;  //注意此处,父子关系非常明确,一定要正确指向 
	return;
}
//根据名字查找编号
int findNum(string name) {
	for(int i=0; i<index; i++) {
		if(name == p[i].name) {
			return p[i].num;  //发现名字,返回名字对应的编号
		}
	}
}
//判断遇到的人有没有在数组中出现过
bool isPeople(string name) {
	for(int i=0; i<index; i++) {
		if(p[i].name==name) {
			return true;
		}
	}
	return false;
}

int main() {
	char c; //第一个字符
	string s; //每次读入的名字
	string fatherName; // 每次读入的父亲的名字
	cin>>c;
	while(c!='$') {
		cin>>s; //读入名字
		//看看当前名名称有没有出现过,如果没有,就初始化 
		if(isPeople(s)==false) {
			p[index].name=s;
			p[index].num=index;
			index++;
		}
		//#号表示 父亲,
		if(c=='#') {
			fatherName=s;  //记录下每次的父亲的名字,为下面合并做准备
		}
		// + 号表示儿子,要进行 合并操作
		if(c=='+') {
			int x = findNum(fatherName); //通过名称查找父亲的编号
			int y = findNum(s); 	//通过名称查找儿子的编号
			//如果不是同一个祖先,就合并
			if(find(x)!=find(y)) {
				merge(x,y);
			}
		}
		// ?号表示询问,查找
		if(c=='?') {
//			for(int i=0; i<index; i++) {
//				cout<<"i="<<i<<",num="<<p[i].num<<",name="<<p[i].name<<endl;
//			}		
			int num= findNum(s); //找到要查找的人的编号
			cout<<p[num].name<<endl;
		}
		cin>>c;
	}
	return 0;
}

【参考答案二:容器】

#include<map>
#include<iostream>
using namespace std;
map<string,string> father;
string getfather(string x)
{
    if(father[x]!=x)
    {
        return getfather(father[x]);
    }
    else
    {
        return father[x];
    }
}
int main()
{
    char c;
    string s,fat;
    cin>>c;
    while(c!='$')
    {
        cin>>s;
        if(c=='#')
        {
            fat=s;
            if(father[s]=="")    father[s]=s;
        }
        if(c=='+')
        {
            father[s]=fat;
        }
        if(c=='?')
        {
            cout<<s<<' '<<getfather(father[s])<<endl;
        }
        cin>>c;
    }
    return 0;
}



扫描二维码推送至手机访问。

版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

标签: 并查集
分享给朋友:

相关文章

【题解】2020-T1 优秀的拆分

【题目描述】一般来说,一个正整数可以拆分成若干个正整数的和。例如,1=1,10=1+2+3+4等。对于正整数n的一种特定拆分,当且仅当在这种拆分下,n被分解为若干个不同的2的正整数次幂。注意,一个数x...

糖果传递

题目描述有n个小朋友坐成一圈,每人有ai个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为1。输入格式第一行一个正整数n≤1000000,表示小朋友的个数.接下来n行,每行一个整数ai,表...

【题解】单词接龙

【题目描述】单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重...

【动态规划】完全背包

【题目描述】设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为m,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于m,而价值的和...

简单算术表达式求值

【题目描述】 两位正整数的简单算术运算(只考虑整数运算),算术运算为:+,加法运算;    -,减法运算;   &nbs...

【题解】切割钢管

【题解】切割钢管

【题目描述】小A是某工地的计算工程师。工地现有 n 根钢管,第 i 根钢管的长度为 ai。现在想用这 n 根钢管来做一个支撑用的柱子。我么可以切割这些钢管成为更短的钢管,但是不能缝合两根钢管。为了安全...