1067 : 最近公共祖先·二

来源:互联网 发布:充值软件的骗局 编辑:程序博客网 时间:2024/06/05 16:40
时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

上上回说到,小Hi和小Ho用非常拙劣——或者说粗糙的手段山寨出了一个神奇的网站,这个网站可以计算出某两个人的所有共同祖先中辈分最低的一个是谁。远在美国的他们利用了一些奇妙的技术获得了国内许多人的相关信息,并且搭建了一个小小的网站来应付来自四面八方的请求。

但正如我们所能想象到的……这样一个简单的算法并不能支撑住非常大的访问量,所以摆在小Hi和小Ho面前的无非两种选择:

其一是购买更为昂贵的服务器,通过提高计算机性能的方式来满足需求——但小Hi和小Ho并没有那么多的钱;其二则是改进他们的算法,通过提高计算机性能的利用率来满足需求——这个主意似乎听起来更加靠谱。

于是为了他们第一个在线产品的顺利运作,小Hi决定对小Ho进行紧急训练——好好的修改一番他们的算法。

而为了更好的向小Ho讲述这个问题,小Hi将这个问题抽象成了这个样子:假设现小Ho现在知道了N对父子关系——父亲和儿子的名字,并且这N对父子关系中涉及的所有人都拥有一个共同的祖先(这个祖先出现在这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^5,M<=10^5, 且数据中所有涉及的人物中不存在两个名字相同的人(即姓名唯一的确定了一个人),所有询问中出现过的名字均在之前所描述的N对父子关系中出现过,第一个出现的名字所确定的人是其他所有人的公共祖先

输出

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:他们的所有共同祖先中辈分最低的一个人的名字。

样例输入
4Adam SamSam JoeySam MichealAdam Kevin3Sam SamAdam SamMicheal Kevin
样例输出
SamAdam

Adam

题解:题目用到了LAC算法,就是先把所有询问读取之后,在dfs中访问到询问的就判断。

         

      如图:dfs过程:先将1,2,3,4遍历完,此时我从4开始,判断询问中是否有4,如果有,在判断询问中另外一个是否访问过,如果访问过,最近祖先必然是另一个的祖先(此时也就是他自身)。该点结束后,更新父节点,4->3,到了3,和4一样,4->2,3->2;到了5,如果询问中有5,并且另外一个访问过,最近祖先必然是另外一个的祖先,以此类推。

 总结:1.根节点的每一个分支的共同祖先必然是根。例如:1的左分支和右分支的最近祖先必然是1;

           2..dfs的目的就是从叶子开始查询。然后更新父节点。每次找到一个节点,就判断是否出现在询问中。

#include <iostream>#include <cstdio>#include <cstring>#include <string>#include <map>#include <vector>using namespace std;map<string,int> mp;vector<int> query[100005]; //全部询问 vector<int> v[100005];  //保存孩子 string name[100005];   //保存名字 int ans[100005];       //保存每一个询问的结果 int fa[100005];        //父节点 string p1[100005];   //保存这是第几个询问的前一个名字 string p2[100005];   //保存这是第几个询问的后一个名字 int k = 0;          //控制每一个字符串的编号 int solve(string& s)       //匹配名字与数字 {if(mp.count(s) != 0){return mp[s];}name[++k] = s;return mp[s] = k;}int find(int x)      //并查集 {if(x == fa[x]){return x;}return fa[x] = find(fa[x]);}void dfs(int x,int pre)        //dfs查找结果 {fa[x] = x;                 //指向自己,表示访问过,也能说明该点所在路径之后的点的最近祖先为x for(int i = 0;i < v[x].size();i++)     //每一个分支开始搜 {dfs(v[x][i],x);}for(int i = 0;i < query[x].size();i++)   //访问该点是否询问 {int t = (x == mp[p1[query[x][i]]] ? mp[p2[query[x][i]]] : mp[p1[query[x][i]]]);if(-1 == fa[t]){continue;}ans[query[x][i]] = find(t);   //找到了 第query[x][i]个询问的答案 }fa[x] = find(pre);        //更新 }int main(){int n,m;cin>>n;string name1,name2;int f,s;for(int i = 0;i < n;i++){cin>>name1>>name2;f = solve(name1);s = solve(name2);v[f].push_back(s);} memset(fa,-1,sizeof(fa));    //表示没有访问过 cin>>m;for(int i = 1;i <= m;i++){cin>>p1[i]>>p2[i];query[mp[p1[i]]].push_back(i);     //query[i][j]的值表示i表示的名字在访问中位置,即第几个询问 query[mp[p2[i]]].push_back(i);}//fa[0] = 0;dfs(1,1);                           //写成dfs(1,0),注意 fa[0]=-1,-1会成为下标 for(int i = 1;i <= m;i++){cout<<name[ans[i]]<<endl;}for(int i = 0;i <= n;i++){v[i].clear();}for(int i = 0;i <= m;i++){query[i].clear();} return 0;}

0 0