首先说一下离线算法:tarjan
该算法基于dfs和并查集。
对于求
LCA(u,v)
,当返回到节点
x
时,必然已经访问完了
实现:
从根节点向下搜索时,搜索完一个节点时,将它放入并查集中,与其父亲节点连边。再判断该节点对应的要询问的节点是否已经被访问过。若已访问过,那么最近公共祖先就是另一个节点在并查集中的fa。
当然,对于多次询问如果一定要按顺序来求解的话,tarjan就无用武之地的。所以对于多组询问,要先保存询问,按照dfs顺来求解。这才是tarjan算法巧妙的地方。
附tarjan模板:
int getroot(int a)
{
if(fa[a]==a)return a;
return fa[a]=getroot(fa[a]);
}
void Union(int a,int b)
{
fa[getroot(a)]=getroot(b);
}
void tarjan(int u)
{
for(node *p=adj[u];p!=NULL;p=p->next)
{
tarjan(p->v);
Union(p->v,u);
}
vis[u]=1;
for(int i=1;i<=ask[u][0];++i)
if(vis[ask[u][i]]==1)
printf("%d\n",getroot(ask[u][i]));
}
void init()
{
scanf("%d",&m);
while(m--)
{
scanf("%d%d",&a,&b);
ask[a][++ask[a][0]]=b;
ask[b][++ask[b][0]]=a;
}
for(int i=1;i<=n;++i)
if(!in[i])
{
tarjan(i);
break;
}
}
而倍增则是在线算法。
通俗地说,就是两个节点同时往上走第一次相遇的地方。
实现:
先预处理出数组
f
得到递推式:
f[i][j]=f[f[i][j−1]][j−1]
同时需要预处理出节点
i
<script type="math/tex" id="MathJax-Element-13">i</script>的深度,这样在倍增时才能保证正确性。
若两个点深度不同,肯定是将深的那个点往上移动直到两者深度一致。
附模板:
void dfs(int u)
{
int v ;
for(node *p=adj[u];p!=NULL;p=p->next)
{
v=p->v;
f[v][0]=u ;
dep[v]=dep[u]+1 ;
dfs(v);
}
}
void init()
{
dep[root]=0 ;
dfs(root);
f[root][0]=root ;
for(int j=1;j<MAXK;++j)
for(int i=1;i<=n;++i)
f[i][j]=f[f[i][j-1]][j-1];
}
void adjust(int &u,int val)
{
for(int j=MAXK-1;j>=0;--j)
if(dep[f[u][j]]>=val)
u=f[u][j];
}
int solve(int u,int v)
{
if(dep[u]>dep[v])adjust(u,dep[v]);
else if(dep[u]<dep[v])adjust(v,dep[u]);
if(u==v)return u;
for(int j=MAXK-1;j>=0;--j)
if(f[u][j]!=f[v][j])
u=f[u][j] ,v=f[v][j];
return f[u][0];
}
习题:
poj1330(一道裸题)
noip2013货车运输(有难度)附题解 题解链接