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; }