LCA(最近公共祖先)的离线算法,用到的是 tarjan 的思想,并用并查集标记父亲节点。
说说我的理解:
我们从根开始深搜遍历树,每当回溯到一个节点时,那就意味着我们已经完成了该节点子树的遍历,显然这个节点就是子树中点以及其本身的最近公共祖先,以此类推到整个树。这里非常巧妙的一点是,对于一个点,只有完成了其子树的遍历,我们才改变其 父节点 的值(赋初值为father[ i ] = i),这样,对于每次询问(就是给出两点标号,要求求出两点间最短距离,算一次询问。假设为 A 和 B),我们搜到 A (或B)时,如果 B (或A)已经被访问过,那么向上回溯,直到第一个 父节点 已经改变的点,就是包括点 A 和点 B 在内子树的根,这就是我们要找的 A 和 B 的最近公共祖先。
推荐自己画图手动实现一下,或者自己出数据用单步调试跟踪一下,了解一下递归过程。
ps:参照http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html
代码:
#include<cstdio>
#include<cstring>
const int N = 40001;
struct Edge{
int e,v;
int next;
}edge[2*N];
int n,m,e_num,head[N];
int x[N],y[N],z[N],f[N],dist[N],vis[N];
void AddEdge(int a,int b,int c){
edge[e_num].e=b; edge[e_num].v=c; edge[e_num].next=head[a]; head[a]=e_num++;
edge[e_num].e=a; edge[e_num].v=c; edge[e_num].next=head[b]; head[b]=e_num++;
}
int find(int x){
if(f[x]!=x)
return f[x]=find(f[x]);
return f[x];
}
void tarjan(int k){
int i;
vis[k]=1;
f[k]=k;
for(i=1;i<=m;i++){//m次询问,z[i]保存的是点 x[i] 和 y[i] 最近公共祖先
if(x[i]==k && vis[y[i]]) z[i]=find(y[i]);
if(y[i]==k && vis[x[i]]) z[i]=find(x[i]);
}
for(i=head[k];i!=-1;i=edge[i].next){
if(!vis[edge[i].e]){
dist[edge[i].e]=dist[k]+edge[i].v;
tarjan(edge[i].e);
f[edge[i].e]=k;
}
}
}
int main()
{
int t,a,b,c,i;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
e_num=0;
memset(head,-1,sizeof(head));
for(i=1;i<n;i++){
scanf("%d%d%d",&a,&b,&c);
AddEdge(a,b,c);
}
for(i=1;i<=n;i++){
x[i]=y[i]=z[i]=0;
}
for(i=1;i<=m;i++){
scanf("%d%d",&a,&b);
x[i]=a; y[i]=b;
}
memset(vis,0,sizeof(vis));
dist[1]=0;
tarjan(1);
for(i=1;i<=m;i++)
printf("%d\n",dist[x[i]]+dist[y[i]]-2*dist[z[i]]);
}
return 0;
}