家谱
题目背景
现代的人对于本家族血统越来越感兴趣。
题目描述
给出充足的父子关系,请你编写程序找到某个人的最早的祖先。
输入输出格式
输入格式:
输入由多行组成,首先是一系列有关父子关系的描述,其中每一组父子关系中父亲只有一行,儿子可能有若干行,用#name的形式描写一组父子关系中的父亲的名字,用+name的形式描写一组父子关系中的儿子的名字;接下来用?name的形式表示要求该人的最早的祖先;最后用单独的一个$表示文件结束。
输出格式:
按照输入文件的要求顺序,求出每一个要找祖先的人的祖先,格式:本人的名字+一个空格+祖先的名字+回车。
输入输出样例
说明
规定每个人的名字都有且只有6个字符,而且首字母大写,且没有任意两个人的名字相同。最多可能有1000组父子关系,总人数最多可能达到50000人,家谱中的记载不超过30代。
其实不看数据的格式的话,这道题就是一道不难的并查集题。但就因为是字符串并查集,才为这道题增添了不少难度。
有两种做法,一是用hash编码在解码,而是比较简洁,用map。
第一种用hash的方法不赖,因为只有6个字符,所以最大的hash值无论如何都不会超过1000,也无需进行mod处理。但不太好办的是解码,因为输出的是人名,并不是hash值。所以每次输出还需O(n)的时间来解码,复杂度不是很好。
第二种用map,这也是我个人比较喜欢的做法。直接把数组的下标和值全都定义成string类型的,就会非常方便,和普通的并查集几乎没什么不同。
但有一点需要注意,就是初始化。因为是迭代器,所以不能直接一个大for跑完把p[i] = i,况且每一个 i 还是一个字符串,不知有多少种情况。
所以采用动态初始化,第一次遇到某个字符串 i 的时候再 p[i] = i。所以还要再开一个map充当vis数组。虽然复杂度多了一个log,但以为数据最大5e4,没什么影响。
给一个用map的方法。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cstring> 5 #include<string> 6 #include<map> 7 using namespace std; 8 const int maxn = 5e4 + 5; 9 map<string, string> p; 10 string Find(string x) 11 { 12 return x == p[x] ? x : p[x] = Find(p[x]); 13 } 14 void merge(string x, string y) //规定x就是y的父亲 15 { 16 string px = Find(x), py = Find(y); 17 if(px == py) return; 18 else {p[py] = px; return;} //y的祖先py指向x的祖先px 19 } 20 string n; 21 map<string, bool> vis; 22 int main() 23 { 24 while(1) 25 { 26 char c; cin >> c; 27 if(c == '$') break; 28 string x; cin >> x; 29 if(c == '#') 30 { 31 if(!vis[x]) {p[x] = x; vis[x] = 1;} //初始化 32 n = x; //用string就比较方便,可以直接赋值 33 } 34 else if(c == '+') 35 { 36 if(!vis[x]) {p[x] = x; vis[x] = 1;} //初始化 37 merge(n, x); 38 } 39 else cout << x << ' ' << Find(x) << endl; 40 } 41 return 0; 42 }