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

【题解】寻找祖先

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

【题目描述】

给出充足的父子关系,请你编写程序找到某个人的最早的祖先。
规定每个人的名字都没有空格,且没有任意两个人的名字相同。最多可能有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;
}



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

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

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

相关文章

2的幂次方表示

【题目描述】任何一个正整数都可以用2的幂次方表示。例如:137=27+23+20同时约定方次用括号来表示,即ab可表示为a(b)。由此可知,137可表示为:2(7)+2(3)+2(0)进一步:7=22...

【题解】报数游戏

【题目描述】路飞在和他朋友们一块玩一个游戏。由于路飞的机智,这个游戏由路飞担任裁判。首先,路飞会给他们一个人一个编号,并且每个人的编号都不相同。接下来的每一个回合,会给一个数,编号不超过它的最大编号的...

【题解】黑白棋子移动

【题目描述】有2n个棋子(n≥4)排成一行,开始位置为白子全部在左边,黑子全部在右边,如下图为n=5的情形:○○○○○●●●●●移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可...

【题解】凯撒密码

【题目描述】恺撒生活在充满危险和阴谋的时代. 恺撒面对的最困难的问题是生存. 为了生存, 他决定创造一种密码. 这种密码听起来难以置信, 如果不知道方法, 没有人可以破解.你是恺撒军队的一个上尉. 你...

【题解】感应门

【题目描述】感应门会在有人经过的时候自动打开,冷却d 秒后自动关闭。如果有人在感应门打开的状态下通过,那么冷却时间会重置,重新冷却d秒后再关闭。在一段时间内,有 n个人陆续通过了感应门,他们...

【题解】奇偶校验

【题目描述】奇偶校验(Parity Check)是一种校验代码传输正确性的方法。根据被传输的一组二进制代码的数位中“1”的个数 是奇数或偶数来进行校验。采用奇数的称为奇校验,反之,称为偶校验。现在给...