Tarjan属于离线做法,即将问题全部储存起来后一起处理一起回答,相比于即问即答的在线做法,tarjan能仅通过一次DFS就能解决所有的LCA问题。
具体很简单,运用了时间戳和并查集。
用visit数组记录某节点是否访问过,用f记录它的father是谁。
先上伪代码
Tarjan(u)//marge和find为并查集合并函数和查找函数 { for each(u,v) //访问所有u子节点v { Tarjan(v); //继续往下遍历 marge(u,v); //合并v到u上 标记v被访问过; } for each(u,e) //访问所有和u有询问关系的e { 如果e被访问过; u,e的最近公共祖先为find(e); } }
从根节点不断往下遍历,回溯的时候将孩子和父亲合并,当一个节点i的所有儿子都遍历完了(包括儿子的儿子等等),就搜寻一遍询问看看能否回答一些,能够回答就回答,不能回答的之后再回答。
至于能不能回答,取决于这个询问涉及到的另一个点是否已经访问过,如果还没访问,则暂时不能回答,如果访问了,则寻找另一个点的father,该father就是它们的最近公共祖先了。
该算法之所以能够找到LCA,是因为一个已经的访问过的点A和另一个正在访问的点B的LCA一定是一个还没结束访问的点C(就是还没遍历完孩子,没有和C的父亲合并的点)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define N 3000002 5 using namespace std; 6 int n,m,num1,num2,ans[N],f[N],u,v,head[N],qhead[N],visit[N]; 7 struct data2{ 8 int next,to,sign; //链式前向星储存(其实sign可以不用) 9 }line[N],qline[N]; 10 void add1(int u,int v){ 11 num1++; 12 line[num1].next=head[u]; 13 line[num1].to=v; 14 line[num1].sign=num1; 15 head[u]=num1; 16 num1++; 17 line[num1].next=head[v]; 18 line[num1].to=u; 19 line[num1].sign=num1; 20 head[v]=num1; 21 } 22 void add2(int u,int v){ //询问建立双向的链式前向星一方面方便查询询问,另一方面确保能回答到问题 23 num2++; 24 qline[num2].next=qhead[u]; 25 qline[num2].to=v; 26 qline[num2].sign=num2; 27 qhead[u]=num2; 28 num2++; 29 qline[num2].next=qhead[v]; 30 qline[num2].to=u; 31 qline[num2].sign=num2; 32 qhead[v]=num2; 33 } 34 int find(int x){ //找父亲 35 if (f[x]==x) return x; 36 f[x]=find(f[x]); 37 return f[x]; 38 } 39 void tarjan(int u){ //Tarjan求LCA 40 f[u]=u; 41 visit[u]=1; //标记该点访问过 42 int v=0,e=0; 43 for (int i=head[u];i!=0;i=line[i].next){ //遍历孩子 44 v=line[i].to; 45 if (!visit[v]) { //没有访问过的深搜 46 tarjan(v); 47 f[v]=u; 48 } 49 } 50 for (int i=qhead[u];i!=0;i=qline[i].next){ //遍历完该点的孩子后查询询问 51 v=qline[i].to; 52 if (visit[v]){ //若该点访问过,则可以知道答案 53 e=find(v); 54 if (qline[i].sign&1) ans[(qline[i].sign+1)/2]=e; 55 else ans[qline[i].sign/2]=e; 56 } 57 } 58 } 59 int main(){ 60 memset(visit,0,sizeof(visit)); 61 scanf("%d%d",&n,&m); //n个点,m个询问 62 num1=0; 63 num2=0; 64 for (int i=1;i<=n-1;i++){ //建图 65 scanf("%d%d",&u,&v); 66 add1(u,v); 67 } 68 for (int i=1;i<=m;i++){ //建立询问图 69 scanf("%d%d",&u,&v); 70 add2(u,v); 71 } 72 tarjan(1); //默认1为根节点 73 for (int i=1;i<=m;i++) 74 printf("%d\n",ans[i]); 75 return 0; 76 }