http://acm.hdu.edu.cn/showproblem.php?pid=2586
LCA的Tarjan离线算法,由于询问的次数比较多,所以用普通的最短路算法肯定行不通
思路:dfs+并查集
用两个邻接表分别保存图和需要求的点
在深搜的过程中,求出所有结点到根结点的距离保存在数组path[ ]中,把所要求的两点的距离保存在father[ ]数组里面,flag[ ]记录结点是否被搜索过
如果需求的是x,y两点间的距离,只需要求出它们的公共祖先k,则father[x,y]=path[x]+path[y]-2*path[k]
在深搜递归回来的时候用并查集压缩路径,如果递归回来到y的时候发现x已经被标记过了,由于路径已经压缩过了,则x和y的公共祖先k必为x的父节点(即find(x))
#include<stdio.h> #include<vector> #include<string.h> #include<stdlib.h> #define M 40005 using namespace std; struct node { int v,w; }; vector<node> g[M]; //存图 vector<node> h[M]; //存需要求的数据 int path[M]; int flag[M]; int father[M]; int p[M]; int n,m; int find(int x) { return x==p[x]?x:p[x]=find(p[x]); } void LCA(int k) { int s,i,si,j,ch; for(i=0;i<g[k].size();i++) { s=g[k][i].v; if(flag[s]==0) { path[s]=path[k]+g[k][i].w; flag[s]=1; //标记为已搜 LCA(s); p[s]=k; //更新父结点 for(j=0;j<h[s].size();j++) { si=h[s][j].v; if(flag[si]==1&&father[h[s][j].w]==0) //判断当前数据是否能够处理,且是否已经处理过了,处理过的将不再处理 { if(si==s) father[h[s][j].w]=0; //相同结点间的距离为0 else { father[h[s][j].w]=path[si]+path[s]-2*path[find(si)]; } } } } } } int main() { int t,a,b,c,i; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { g[i].clear(); h[i].clear(); path[i]=0; flag[i]=0; p[i]=i; father[i]=0; } node t; for(i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); t.v=a;t.w=c; g[b].push_back(t); t.v=b; g[a].push_back(t); } for(i=1;i<=m;i++) { scanf("%d%d",&a,&b); t.v=a;t.w=i; h[b].push_back(t); t.v=b; h[a].push_back(t); } path[1]=0; flag[1]=1; LCA(1); for(i=1;i<=m;i++) printf("%d\n",father[i]); } return 0; }