题目1 : 最近公共祖先·一
描述
小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中,但这是为什么呢?
“为什么呢?”小Hi如是问道,在他的观察中小Ho已经沉迷这个网站一周之久了,甚至连他心爱的树玩具都弃置一边。
“嘿嘿,小Hi,你快过来看!”小Ho招呼道。
“你看,在这个对话框里输入我的名字,在另一个对话框里,输入你的名字,再点这个查询按钮,就可以查出来……什么!我们居然有同一个祖祖祖祖祖爷爷?”
“诶,真是诶……这个网站有点厉害啊。”小Hi不由感叹道。
“是啊,这是什么算法啊,这么厉害!”小Ho也附和道。
“别2,我说的是他能弄到这些数据很厉害,而人类的繁殖树这种层数比较浅的树对这类算法的要求可是简单的不得了,你都能写出来呢!”小Hi道。
“啊?我也能写出来?可是……该从哪开始呢?”小Ho困惑了。
小Ho要面临的问题是这样的,假设现在他知道了N个人的信息——他们的父亲是谁,他需要对于小Hi的每一次提问——两个人的名字,告诉小Hi这两个人的是否存在同一个祖先,如果存在,那么他们的所有共同祖先中辈分最低的一个是谁?
输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第1行为一个整数N,意义如前文所述。
每组测试数据的第2~N+1行,每行分别描述一对父子关系,其中第i+1行为两个由大小写字母组成的字符串Father_i, Son_i,分别表示父亲的名字和儿子的名字。
每组测试数据的第N+2行为一个整数M,表示小Hi总共询问的次数。
每组测试数据的第N+3~N+M+2行,每行分别描述一个询问,其中第N+i+2行为两个由大小写字母组成的字符串Name1_i, Name2_i,分别表示小Hi询问中的两个名字。
对于100%的数据,满足N<=10^2,M<=10^2, 且数据中所有涉及的人物中不存在两个名字相同的人(即姓名唯一的确定了一个人)。
输出
对于每组测试数据,对于每个小Hi的询问,输出一行,表示查询的结果:如果根据已知信息,可以判定询问中的两个人存在共同的祖先,则输出他们的所有共同祖先中辈分最低的一个人的名字,否则输出-1。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一些看法:
这是一道非传统意义上的算法题,因为它没有考到任何成型的算法思想(如:动态规划、费用流、图论等),甚至连搜索不用.....
从这个层面上来讲,这是一道比较简单的题目。但解这道题并不需要任何的先验的算法知识,只要会写C/Java都能解。
因此,这题考察的是对问题的理解,隐含条件的捕捉,以及将思路转换为代码的能力。虽然不难,但还是很有新意的,是技术岗笔试很好的选题参考。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Solution:
这道题的关键是一个常识中的常识:虽然一个爸爸可能有很多儿子,但每个儿子都只可能有一个爸爸。
根据这一点,对于任意的点P,其祖先的个数为n<-[0,N)。
接下来,我们要找Pa与Pb的共同的祖先。最简单的思路,就是遍历 {Pa的祖先}*{Pb的祖先}的所有组合,O(N^2)的时间复杂度,单点1000ms应该能过,M(<=100)点10s能不能过未知。
Anyway,即使过了该题的数据,这个方法显然很呆萌.......anything but 机智~~
让我们更机智一点吧~
先整理一下思路~
对于一点P,我们可以求出他的祖先序列 Plist=[p(1)...p(i),p(i+1)...p(n)],其中p(i+1)是p(i)的唯一的父亲。
因此,在Plist中,任意一点p(i)->p(n)的序列是唯一的。
因此,如果Pa,Pb存在公共祖先,即存在i,j使得Pa_list(i)=Pb_list(j)的话,必定有Pa_list(i+1)=Pb_list(j+1),
进而有Pa_list(na)=Pb_list(na-i+j) 或者 Pa_list(na-j+i)=Pb_list(nb)。
简而言之,如果存在公共祖先,则两个祖先序列中的其中一个的老祖宗,必定也存在于另一个祖先序列中。因此,我们只需要O(2*N)即能确定是否存在祖先了。
之后,只要不断回溯序列,找到辈分最小的共同祖先即可。
回顾整个解题过程,不难发现,祖先序列的唯一性(单支性)是解题的关键。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下面贴出AC代码以供参考,建议有兴趣的童鞋们自己写一遍(记得在每次输出后加上换行符哟^ ^):
#include <iostream>
#include <map>
#include <string>
#include <string.h>
using namespace std; int M,N;
map<string,string> mp;
string lst[][];
int len[]; int find_root(int j){
int i=;
while(mp.count(lst[j][i])>){
lst[j][i+]=mp[lst[j][i]];
i++;
}
return i;
} void sourcing(){
int a[];
for(int j=;j<;j++)
len[j]=find_root(j); bool b=false;
int la=len[],lb=len[];
while(la>=&&lb>=){
if(lst[][len[]]==lst[][lb]){
la=len[];
b=true;
break;
}
if(lst[][len[]]==lst[][la]){
lb=len[];
b=true;
break;
}
la--;lb--;
}
if(b){
while(la>=&&lb>=)
if(lst[][la]==lst[][lb]){
la--;lb--;
}else{
break;
}
la++;lb++;
cout<<lst[][la]<<"\n";
}else{
cout<<-<<"\n";
}
} int main(){
cin>>N;
string a,b;
for(int i=;i<N;i++){
cin>>a>>b;
mp[b]=a;
}
cin>>M;
for(int i=;i<M;i++){
cin>>lst[][]>>lst[][];
sourcing();
} return ;
}