LCA的离线算法。复杂度为O(n+q)。
这个算法充分利用了dfs树的结构。
对于每个节点u,关于它的询问(u,v)只有两种。(假设先dfs(u)后dfs(v))
1、v在u的子树内。
此时LCA(u,v) = u.
2、v不在u的子树内。
⑴假设v在u的父亲的另一棵子树内。
此时LCA(u,v) = father[u].
⑵如果不满足条件⑴,则v可能在u的父亲的父亲的另一棵子树内。
而此时LCA(u,v) = father[ father[u] ].
⑶……
观察一下,是不是发现了什么呢?
没错,不论是哪种情况,LCA(u,v)都与u和father[ ]有某种关系。我们能不能抓住这种关系呢?
我们继续观察,一直向上取father[ ],貌似和并查集的FIND操作很像呢。
我们用并查集的角度依次考虑上面的情况试试看。
1、v在u的子树内。
此时dfs(u)还在栈中,没有执行完,此时没有向上取father[ ],说明此时u是根。
2、v不在u的子树内。
⑴假设v在u的父亲的另一棵子树内。
此时的dfs(u)已经执行完并出栈。此时向上取了一次father[ ],说明此时u的父亲是根。
⑵如果不满足条件⑴,则v可能在u的父亲的父亲的另一棵子树内。
同理,此时dfs(u的父亲)也已经执行完并出栈。此时向上取了两次father[ ],说明此时u的父亲的父亲是根。
⑶……
综上,我们只要保证当dfs(u)在栈中的时候,u是根;当dfs(u)不在栈中的时候,father[u]是根就行了。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define FileIn freopen("in.ads","r",stdin)
#define FileOut freopen("out.ads","w",stdout)
#define N 155
#define M 22555
struct Vertex
{
int head;
}V[N],Qv[N];
struct Edge
{
int v,next;
}E[M],Qe[M];
int top,pre[N];
bool used[N];
void Init()
{
top = 0;
memset(V,-1,sizeof(V));
memset(Qv,-1,sizeof(Qv));
memset(pre,-1,sizeof(pre));
memset(used,0,sizeof(used));
}
int Root(int x)
{
if(pre[x] != -1)
return pre[x] = Root(pre[x]);
else
return x;
}
void Union(int a,int b)
{
int r1 = Root(a);
int r2 = Root(b);
if(r1 != r2)
pre[r2] = r1;
}
void Add_Edge(int u,int v)
{
E[top].v = v;
E[top].next = V[u].head;
V[u].head = top++;
}
void Add_Qedge(int u,int v)
{
Qe[top].v = v;
Qe[top].next = Qv[u].head;
Qv[u].head = top++;
}
void Tarjan(int u)
{
used[u] = true;
for(int i=Qv[u].head;i!=-1;i=Qe[i].next)
{
int v = Qe[i].v;
if(used[v])
printf("The LCA of (%d,%d) is -> %d\n",u,v,Root(v));
}
for(int i=V[u].head;i!=-1;i=E[i].next)
{
int v= E[i].v;
if(used[v])
continue;
Tarjan(v);
Union(u,v);
}
}
int main()
{
FileIn;
int n,m,u,v,Q;
while(~scanf("%d%d",&n,&m))
{
Init();
while(m--)
{
scanf("%d%d",&u,&v);
Add_Edge(u,v);
Add_Edge(v,u);
}
scanf("%d",&Q);
while(Q--)
{
scanf("%d%d",&u,&v);
Add_Qedge(u,v);
Add_Qedge(v,u);
}
Tarjan(1);
}
return 0;
}