LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
参考博客:
http://www.cnblogs.com/scau20110726/archive/2013/05/26/3100812.html
离线
LCA的离线算法一般是基于搜索的Tarjan算法。
思想:
Tarjan是一个离线算法,先把所有的询问保存下来,然后开始Tarjan搜索。从根节点开始形成一颗深搜树,从任意节点u出发,遍历其所有子节点。这样在回溯u时其所有子节点已经遍历,这时再把u放入合并集合中,这样u节点和所有之前遍历的u节点的子树枝节点的lca就是u,然后在处理关于u的询问。这样在Tarjan的一遍深搜之后就已经把所有询问的答案保存下来,最后按询问顺序输出即可。
伪代码
vis[i] //i节点是否遍历过
Find(i) //并查集中找到i的祖先
f[i] //i节点的祖先节点
void Tarjan(int u)
{
vis[u] = true; //置u节点为已遍历
f[u]=u; //初始u节点的祖先节点为u
for(u的所有儿子v)
if(该儿子v没有被访问)
{
Tarjan(v);
Union(u,v); //将u的子节点v的祖先置为u
}
for(包含u的所有询问(u,v))
if( v遍历过 ) //说明v和u是同一节点(可能是u或v)的子节点
LCA(u,v) = LCA(v,u) = Find(v);
}
假设在询问中u,v是同一节点t的子节点
那么在回溯到u处理关于u的询问的时候,v一定已经完成了Tarjan(说明已经将v的祖先节点置为t),所以LCA(u,v)会等于t。
假设在询问中u,v是同一节点u的子节点
同理在回溯到u处理关于u的询问的时候,v一定已经完成了Tarjan(说明已经将v的祖先节点置为u),所以LCA(u,v)会等于u。
假设在询问中u,v是同一节点v的子节点
同理在回溯到u处理关于u的询问的时候,v的祖先节点都还未完成其子节点v的Tarjan(说明此时v的祖先节点仍为v),所以LCA(u,v)会等于v。
可以自己画棵树照上面的伪代码体会一下。
在线
LCA的在线算法就是转化成RMQ问题然后用ST算法解决。
这里问题的关键就是怎么把LCA转化成RMQ问题。其实就是通过dfs,然后用数组记录节点出现的顺序,深度以及每个节点第一次出现的位置,之后就转化成RMQ问题求解。
图基本已经说的很清楚了,但它用一个pos数组保存最小值下标,其实这是不必要的。因为我们现在要求的就是最小值对应的下标,所以我们可以直接用dp数组保存下标,然后转移下标即可。
看看代码就明白了。
void ST(int len)
{
for(int i=1;i<=len;i++) dp[i][0]=i;
int k = (int)(log((double)len)/log(2.0));
for(int j=1;j<=k;j++)
{
for(int i=1;i+(1<<j)-1<=len;i++)
{
int a = dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
dp[i][j] = deep[a]<deep[b] ? a : b;
}
}
}
int RMQ(int l,int r)
{
int k = (int)(log((double)(r-l+1))/log(2.0));
int a=dp[l][k],b=dp[r-(1<<k)+1][k];
return deep[a]<deep[b] ? a : b;
}
hdu2586
题意
给定一棵树,每条边都有一定的权值,m次询问,每次询问某两点间的距离。
题解
可以转化成LCA来解。首先找到u, v 两点的lca,然后计算一下距离值就可以了。这里的计算方法是,记下根结点到任意一点的距离dis[],这样ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]。
在线(RMQ-ST)代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 4e4+5;
int n,m;
int dp[maxn*2][20];
int p[maxn*2],deep[maxn*2],first[maxn],dis[maxn];
bool vis[maxn];
struct node{
int to,w,next;
}edge[maxn*2];
int tot,head[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(vis,false,sizeof(vis));
memset(dis,0,sizeof(dis));
}
void add_edge(int u,int v,int w)
{
edge[tot].to = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot++;
edge[tot].to = u;
edge[tot].w = w;
edge[tot].next = head[v];
head[v] = tot++;
}
void dfs(int u,int dep)
{
p[++tot]=u;
first[u]=tot;
deep[tot]=dep;
vis[u]=true;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v = edge[i].to;
if(!vis[v])
{
int w = edge[i].w;
dis[v] = dis[u]+w;
dfs(v,dep+1);
p[++tot]=u;
deep[tot]=dep;
}
}
}
void ST(int len)
{
for(int i=1;i<=len;i++) dp[i][0]=i;
int k = (int)(log((double)len)/log(2.0));
for(int j=1;j<=k;j++)
{
for(int i=1;i+(1<<j)-1<=len;i++)
{
int a = dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
dp[i][j] = deep[a]<deep[b] ? a : b;
}
}
}
int RMQ(int l,int r)
{
int k = (int)(log((double)(r-l+1))/log(2.0));
int a=dp[l][k],b=dp[r-(1<<k)+1][k];
return deep[a]<deep[b] ? a : b;
}
int LCA(int u,int v)
{
int l=first[u],r=first[v];
if(l>r) swap(l,r);
return p[RMQ(l,r)];
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
int u,v,w;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
}
tot=0;
dfs(1,1);
ST(tot);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
printf("%d\n",dis[u]+dis[v]-2*dis[LCA(u,v)]);
}
}
return 0;
}
离线(Tarjan)代码
#include <bits/stdc++.h>
using namespace std;
const int N=40005,M=205;
int vis[N],root[N],dis[N],ans[M][3],n,m,num1,num2;
struct node{
int v,d;
node *next;
};
node *link1[N],*link2[N],edg1[N*2],edg2[N*2];
void add(int a,int b,int c,node *link[],node edg[],int &num)
{
edg[num].v=a;
edg[num].d=c;
edg[num].next=link[b];
link[b]=edg+num++;
edg[num].v=b;
edg[num].d=c;
edg[num].next=link[a];
link[a]=edg+num++;
}
int find_root(int a)
{
return a==root[a] ? a : root[a]=find_root(root[a]);
}
void munion(int a,int b)
{
a = find_root(a);
b = find_root(b);
if(a==b) return;
root[b]=a;
}
void init()
{
num1=num2=0;
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
memset(link1,0,sizeof(link1));
memset(link2,0,sizeof(link2));
}
void Tarjan(int x)
{
vis[x]=1;
root[x]=x;
for(node *p=link1[x];p;p = p->next)
{
if(!vis[p->v])
{
dis[p->v]=dis[x]+p->d;
Tarjan(p->v);
munion(x,p->v);
}
}
for(node *p=link2[x];p;p=p->next)
{
if(vis[p->v])
{
ans[p->d][2]=find_root(p->v);
}
}
}
int main()
{
int T,a,b,c;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
init();
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c,link1,edg1,num1);
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
add(a,b,i,link2,edg2,num2);
ans[i][0]=a;ans[i][1]=b;
}
Tarjan(1);
for(int i=1;i<=m;i++) printf("%d\n",dis[ans[i][0]]+dis[ans[i][1]]-2*dis[ans[i][2]]);
}
return 0;
}
比较(上面是在线,下面是离线)