青少年编程知识记录 codecoming

【题解】寻找祖先

【题目描述】

给出充足的父子关系,请你编写程序找到某个人的最早的祖先。

规定每个人的名字都没有空格,且没有任意两个人的名字相同。最多可能有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;  }





(adsbygoogle = window.adsbygoogle || []).push({});

标签: 并查集

作者:亿万年的星光 分类:题解目录 浏览: