题目链接:L2-030 冰岛人 (25分)
2018年世界杯,冰岛队因1:1平了强大的阿根廷队而一战成名。好事者发现冰岛人的名字后面似乎都有个“松”(son),于是有网友科普如下:
冰岛人沿用的是维京人古老的父系姓制,孩子的姓等于父亲的名加后缀,如果是儿子就加 sson,女儿则加 sdottir。因为冰岛人口较少,为避免近亲繁衍,本地人交往前先用个 App 查一下两人祖宗若干代有无联系。本题就请你实现这个 App 的功能。
输入格式:
输入首先在第一行给出一个正整数 N(1<N≤10^5 ),为当地人口数。随后 N 行,每行给出一个人名,格式为:名 姓(带性别后缀),两个字符串均由不超过 20 个小写的英文字母组成。维京人后裔是可以通过姓的后缀判断其性别的,其他人则是在姓的后面加 m 表示男性、f 表示女性。题目保证给出的每个维京家族的起源人都是男性。
随后一行给出正整数 M,为查询数量。随后 M 行,每行给出一对人名,格式为:名1 姓1 名2 姓2。注意:这里的姓是不带后缀的。四个字符串均由不超过 20 个小写的英文字母组成。
题目保证不存在两个人是同名的。
输出格式:
对每一个查询,根据结果在一行内显示以下信息:
若两人为异性,且五代以内无公共祖先,则输出 Yes;
若两人为异性,但五代以内(不包括第五代)有公共祖先,则输出 No;
若两人为同性,则输出 Whatever;
若有一人不在名单内,则输出 NA。
所谓“五代以内无公共祖先”是指两人的公共祖先(如果存在的话)必须比任何一方的曾祖父辈分高。
输入样例:
15
chris smithm
adam smithm
bob adamsson
jack chrissson
bill chrissson
mike jacksson
steve billsson
tim mikesson
april mikesdottir
eric stevesson
tracy timsdottir
james ericsson
patrick jacksson
robin patricksson
will robinsson
6
tracy tim james eric
will robin tracy tim
april mike steve bill
bob adam eric steve
tracy tim tracy tim
x man april mikes
输出样例:
Yes
No
No
Whatever
Whatever
NA
解题思路:
-
第一次写这题的时候,以为一个DFS就可以解决(跟之前那个天下姐妹那题好像),但是还是有所不同,看下图
-
愿天下有情人都是失散多年的兄妹:
-
冰岛人:
-
所以,很明显姐妹那道DFS就行,但是DFS来处理这道题会很麻烦,那就用并查集的思路,解决它
题解:
- 首先,字符串转换为整型来存储每一个节点,用map转换
- 先进行输入处理来存储姓名,并判断是否为男、女。
- 再对存储的数据,来进行转换为关系图,如下图
- 最后,进行判断,先判断是否再名单,然后判断是否同sex,最后判断是否在五代以内有公共父节点
判断父节点,记录出现的次数和距离,距离小于4就不行
图片转载自:梅菜扣rou大佬
代码如下:
#include<iostream>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
#define mm(a,x) memset(a,x,sizeof a)
const int N = 1e5 + 10;
int n,k;
//初始化为-1
vector<int > f(N,-1);
vector<string > g[N];
bool sex[N];
map<string,int > m;
string f1,f2;
int cnt;
int count[N],dist1[N],dist2[N];
bool judge(int a,int c){
mm(count,0);mm(dist1,0);mm(dist2,0);
//count记录某人出现的次数
count[a] ++;count[c] ++;
int t;
while(f[a] != -1){
t = f[a];
count[t] ++;
//更新与根节点的距离
dist1[t] = dist1[a] + 1;
if(t == c){ //直系
return false;
}
a = t;
}
while(f[c] != -1){
t = f[c];
count[t] ++;
dist2[t] = dist2[c] + 1;
//出现次数大于1次,就表示有公共父节点了
if(count[t] > 1){
//判断是否在五代以内,不在就可以,在就不可以
if(dist1[t] >= 4 && dist2[t] >= 4){
return true;
}else{
return false;
}
}
c = t;
}
return true;
}
int main(){
cin >> n;
for(int i = 1; i <= n; i ++ ){
cin >> f1 >> f2;
m[f1] = ++ cnt;
int len = f2.size();
if(f2[len - 1] == 'm' || f2[len - 1] == 'n'){ //m和n都是男的
sex[cnt] = true;;
}else{//r是女
sex[cnt] = false;
}
//先把它的姓名存起来,为下面构成家谱做铺垫
g[i].push_back(f1);
g[i].push_back(f2);
}
string par;
for(int i = 1; i <= n; i ++ ){
f1 = g[i][0];
f2 = g[i][1];
int len = f2.size();
if(f2[len - 1] != 'r' && f2[len - 1] != 'n'){ //不是r也不是n,那就是祖宗呀
continue;
}
if(sex[m[f1]]){
par = f2.substr(0,len - 4);
}else{
par = f2.substr(0,len - 7);
}
f[m[f1]] = m[par];
}
cin >> k;
while(k -- ){
string a,b,c,d;
cin >> a >> b >> c >> d;
//首先判断在不在名单里面
if(m.find(a) == m.end() || m.find(c) == m.end()){
cout<<"NA\n";
continue;
}
//其次判断是不是同性别
if(sex[m[a]] == sex[m[c]]){
cout<<"Whatever\n";
continue;
}
//最后,看公共父节点是否在五代以内
if(judge(m[a],m[c])){
cout<<"Yes\n";
}else{
cout<<"No\n";
}
}
return 0;
}