上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的“最近公共祖先”网站,但是很快这样一个离线算法就出现了问题:如果只有一个人提出了询问,那么小Hi和小Ho很难决定到底是针对这个询问就直接进行计算还是等待一定数量的询问一起计算。毕竟无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的。
那么问题就来了,如果每次计算都只针对一个询问进行的话,那么这样的算法事实上还不如使用最开始的朴素算法呢!但是如果每次要等上很多人一起的话,因为说不准什么时候才能够凑够人——所以事实上有可能要等上很久很久才能够进行一次计算,实际上也是很慢的!
“那到底要怎么办呢?在等到10分钟,或者凑够一定数量的人两个条件满足一个时就进行运算?”小Ho想出了一个折衷的办法。
“哪有这么麻烦!别忘了和离线算法相对应的可是有一个叫做在线算法的东西呢!”小Hi笑道。
小Ho面临的问题还是和之前一样:假设现在小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的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:他们的所有共同祖先中辈分最低的一个人的名字。
样例输入
4 Adam Sam Sam Joey Sam Micheal Adam Kevin 3 Sam Sam Adam Sam Micheal Kevin样例输出
Sam Adam Adam
分析:LCA的在线算法,RMQ-ST算法。留存作为模板。
从树的根节点开始进行深度优先搜索,每次经过某一个点——无论是从它的父亲节点进入这个点,还是从它的儿子节点返回这个点,都按顺序记录下来。这样,就把一棵树转换成了一个数组。而找到树上两个节点的最近公共祖先,无非就是找到这两个节点第一次出现在数组中的位置所囊括的一段区间中深度最小的那个点。所以,方法也就出来了。
步骤:
1)dfs计算出每个节点的深度depth[],每个节点第一次出现的位置first[]。id[]数组保存当前节点。
2)RMQ-ST算法进行预处理,minq[i][j]表示的是从i开始,长度为2^j区间内的深度最小值的下标。
3)直接输出结果。
RMQ-ST算法解释:http://blog.csdn.net/jhgkjhg_ugtdk77/article/details/47298969
题目链接:http://hihocoder.com/problemset/problem/1069
代码清单:
#include<set> #include<map> #include<cmath> #include<queue> #include<stack> #include<ctime> #include<cctype> #include<string> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; const int maxi = 20 + 5 ; const int maxn = 100000 + 5; const int maxv = 100000 + 5; struct G{ int v,next; }graph[2*maxn]; int N,M; string name1,name2; string rstr[maxn]; map<string,int>idx; int iid,idx1,idx2,num,idd; int head[2*maxn]; int minq[2*maxn][maxi]; //这个数组记得开到2*N,因为遍历后序列长度为2*n-1 int first[2*maxn]; //每个节点在遍历序列中第一次出现的位置 int id[2*maxn]; //保存遍历的节点序列,长度为2n-1,从下标1开始保存 int depth[2*maxn]; //和遍历序列对应的节点深度数组,长度为2n-1,从下标1开始保存 bool vis[maxn]; void init(){ idx.clear(); iid=idd=num=0; memset(head,-1,sizeof(head)); memset(minq,0x7f,sizeof(minq)); memset(first,0,sizeof(first)); memset(id,0,sizeof(id)); memset(depth,0,sizeof(depth)); memset(vis,false,sizeof(vis)); } int get_idx(string name){ if(idx.count(name)) return idx[name]; idx[name]=++iid; rstr[iid]=name; return iid; } void addg(int u,int v){ graph[num].v=v; graph[num].next=head[u]; head[u]=num++; } void input(){ scanf("%d",&N); for(int i=1;i<=N;i++){ cin>>name1>>name2; idx1=get_idx(name1); idx2=get_idx(name2); addg(idx1,idx2); addg(idx2,idx1); } } void dfs(int u,int dep){ vis[u]=true; id[++idd]=u; depth[idd]=dep; first[u]=idd; for(int i=head[u];i!=-1;i=graph[i].next){ int v=graph[i].v; if(vis[v]) continue; dfs(v,dep+1); id[++idd]=u; depth[idd]=dep; } } int min(int a,int b){ return depth[a]<depth[b] ? a : b ; } void RMQ_ST(){ int L=(int)((log(idd))/(log(2.0))); for(int i=1;i<=idd;i++) minq[i][0]=i; for(int j=1;j<=L;j++){ for(int i=1;i+(1<<j)-1<=idd;i++){ minq[i][j]=min(minq[i][j-1],minq[i+(1<<(j-1))][j-1]); } } } string RMQ(int l,int r){ if(l>r) swap(l,r); int mi=(int)((log(r-l+1))/(log(2.0))); int pose=min(minq[l][mi],minq[r-(1<<mi)+1][mi]); return rstr[id[pose]]; } void solve(){ dfs(1,0); RMQ_ST(); scanf("%d",&M); for(int i=1;i<=M;i++){ cin>>name1>>name2; idx1=first[get_idx(name1)]; idx2=first[get_idx(name2)]; cout<<RMQ(idx1,idx2)<<endl; } } int main(){ init(); input(); solve(); return 0; }