有关概念:
最近公共祖先(LCA,Lowest Common Ancestors):对于有根树T的两个结点u、v,最近公共祖先表示u和v的深度最大的共同祖先。
Tarjan是求LCA的离线算法(先存储所有询问,再进行运算)
思路:
从根结点开始DFS,对遍历到的结点u标记已访问,创建新集合,元素为u,再遍历u的每一个儿子,回溯时将每个儿子的集合并到u的集合上,用并查集记录集合中的每个元素的fa为u,接着处理询问,对于关于u的每一个询问,若另一个结点v已访问,则可断定LCA(u,v)为fav,记录结果,最后按顺序输出即可
dis存储该结点到根结点的距离,用于计算两点之间的路径长度
样例推导(样例来自@SHHHS):
求8、6,9、7的LCA
从根结点1进入,标记访问,创建集合
访问2、4,回溯到2,新集合包含两个结点
访问5、8,处理8的询问,但6未访问,跳过
访问9,处理询问,7未访问,跳过,5、8和9并入2的集合(即fa值设为2)
访问6,处理询问,LCA(8,6)为fa8,即2
访问10,回溯,并入6的集合
回溯,并入2的集合
并入1的集合
访问3、7,处理询问,LCA(7,9)为fa9,即1
回溯,并入3的集合
并入1的集合
1 #include<cstdio> 2 #define MAXN 3 #define MAXQ 4 int n,Q,heade[MAXN],headq[MAXN],fa[MAXN],lca[MAXQ],dis[MAXN],cnt; 5 bool vis[MAXN]; 6 struct edge 7 { 8 int v,next,val; 9 }e[MAXN*2]; 10 struct query 11 { 12 int u,v,next; 13 }q[MAXQ*2]; 14 void adde(int x,int y,int z) 15 { 16 e[++cnt].v=y; 17 e[cnt].next=heade[x]; 18 heade[x]=cnt; 19 e[cnt].val=z; 20 } 21 void addq(int x,int y) 22 { 23 q[++cnt].u=x; 24 q[cnt].v=y; 25 q[cnt].next=headq[x]; 26 headq[x]=cnt; 27 } 28 int getfa(int x)//并查集路径压缩 29 { 30 return fa[x]=x==fa[x]?x:getfa(fa[x]); 31 } 32 int getdis(int i)//计算路径长度 33 { 34 return dis[q[i<<1].u]+dis[q[i<<1].v]-2*dis[lca[i]]; 35 } 36 void Tarjan(int u) 37 { 38 fa[u]=u; 39 vis[u]=true; 40 for(int i=heade[u];i;i=e[i].next) 41 { 42 int v=e[i].v; 43 if(!vis[v]) 44 { 45 dis[v]=dis[u]+e[i].val; 46 Tarjan(v); 47 fa[v]=u; 48 } 49 } 50 for(int i=headq[u];i;i=q[i].next)//处理询问 51 { 52 int v=q[i].u==u?q[i].v:q[i].u; 53 if(vis[v])lca[i>>1]=getfa(fa[v]); 54 } 55 } 56 int main() 57 { 58 scanf("%d",&n); 59 int x,y,z; 60 for(int i=1;i<n;i++) 61 { 62 scanf("%d%d%d",&x,&y,&z); 63 adde(x,y,z); 64 adde(y,x,z); 65 } 66 cnt=1; 67 scanf("%d",&Q); 68 for(int i=1;i<=Q;i++) 69 { 70 scanf("%d%d",&x,&y); 71 addq(x,y); 72 addq(y,x); 73 } 74 Tarjan(1); 75 for(int i=1;i<=Q;i++) 76 { 77 printf("%d ",getdis(i)); 78 } 79 return 0; 80 }