在求解最近公共祖先为问题上,用到的是Tarjan的思想,从根结点开始形成一棵深树,非常好的处理技巧就是在回溯到结点u的时候,u的子树已经遍历,这时候才把u结点放入合并集合中,这样u结点和所有u的子树中的结点的最近公共祖先就是u了,u和还未遍历的所有u的兄弟结点及子树中的最近公共祖先就是u的父亲结点。以此类推。。这样我们在对树深度遍历的时候就很自然的将树中的结点分成若干的集合,两个集合中的所属不同集合的任意一对顶点的公共祖先都是相同的,也就是说这两个集合的最近公共最先只有一个。对于每个集合而言可以用并查集来优化,时间复杂度就大大降低了,为O(n + q),n为总结点数,q为询问结点对数。
http://acm.hdu.edu.cn/showproblem.php?pid=2586
const int Max_N = 40008 ; const int Max_M = 208 ; struct Edge{ int v ; int w ; int next ; }edge[Max_N*2]; int id , List[Max_N] ; void add_edge(int u , int v , int w){ edge[id].v = v ; edge[id].w = w ; edge[id].next = List[u] ; List[u] = id++ ; } struct Q{ int v ; int id ; Q(){} Q(int _v , int _id):v(_v),id(_id){} }; vector<Q> Query[Max_N] ; bool visited[Max_N] ; int dist[Max_N] ; int father[Max_N] ; int lca[Max_M] ; int q[Max_M][2] ; int find_father(int x){ if(x == father[x]) return x ; return father[x] = find_father(father[x]) ; } void tarjan(int u){ int e , v , w , i , qv , qid ; visited[u] = 1 ; for(e = List[u] ; e != -1 ; e = edge[e].next){ v = edge[e].v ; w = edge[e].w ; for(i = 0 ; i < Query[u].size() ; i++){ qv = Query[u][i].v ; qid = Query[u][i].id ; if(visited[qv]) lca[qid] = find_father(qv) ; } if(! visited[v]){ dist[v] = dist[u] + w ; tarjan(v) ; father[v] = u ; } } } int main(){ int T , u , v , w , i , N , M ; cin>>T ; while(T--){ cin>>N>>M ; id = 0 ; memset(List , -1 , (N+1)*sizeof(int)) ; for(i = 1 ; i <= N ; i++){ Query[i].clear() ; father[i] = i ; } for(i = 1 ; i < N ; i++){ scanf("%d%d%d" ,&u , &v , &w) ; add_edge(u , v , w) ; add_edge(v , u , w) ; } for(i = 1 ; i <= M ; i++){ scanf("%d%d" ,&u ,&v) ; Query[u].push_back(Q(v , i)) ; Query[v].push_back(Q(u , i)) ; q[i][0] = u ; q[i][1] = v ; } memset(visited , 0 , (N+1)*sizeof(bool)) ; dist[1] = 0 ; tarjan(1) ; for(i = 1 ; i <= M ; i++){ u = q[i][0] ; v = q[i][1] ; printf("%d\n" , dist[u] + dist[v] - 2*dist[lca[i]]) ; } } return 0 ; }