HDU 2586 How far away ?
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 11320 Accepted Submission(s):
4119
Problem Description
There are n houses in the village and some
bidirectional roads connecting them. Every day peole always like to ask like
this "How far is it if I want to go from house A to house B"? Usually it hard to
answer. But luckily int this village the answer is always unique, since the
roads are built in the way that there is a unique simple path("simple" means you
can‘t visit a place twice) between every two houses. Yout task is to answer all
these curious people.
Input
First line is a single integer T(T<=10), indicating
the number of test cases.
For each test case,in the first line there are
two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses
and the number of queries. The following n-1 lines each consisting three numbers
i,j,k, separated bu a single space, meaning that there is a road connecting
house i and house j,with length k(0<k<=40000).The houses are labeled from
1 to n.
Next m lines each has distinct integers i and j, you areato answer
the distance between house i and house j.
Output
For each test case,output m lines. Each line represents
the answer of the query. Output a bland line after each test case.
Sample Input
2
3 2
1 2 10
3 1 15
1 2
2 3
2 2
1 2 100
1 2
2 1
Sample Output
10
25
100
100
Source
ECJTU
2009 Spring Contest
解析:tarjan离线算法求LCA:
概念描述:
LCA(Least Common Ancestors):即最近公共祖先,是指这样一个问题:在有根树中,找出某两个结点u和v的所有祖先中距离(u,v)最近的那个公共祖先(也就是离根最远的那个公共祖先)。
时间和空间复杂度:
- 时间复杂度:当询问次数为Q,节点数为N时,时间复杂度为O(N+Q)。
- 空间复杂度:①建图时存储的空间大小(树的节点个数个空间);②深搜时遍历树时需要的空间大小(树的最大深度)
算法实现基于基本原理:
算法是基于DFS和并查集来实现的。
算法流程 及 对求LCA(u,v)的证明:
- 从根节点(root)开始深搜,
- 对于新搜索到的一个节点(x),首先创建由这个结点构成的集合(由par数组维护,par[x]=x,当前这个集合中只有元素x),
- 然后依次搜索并处理该节点包含的所有子树(搜素和处理是个递归的过程。结合第5点理解:每搜索完一棵子树,则可确定子树内的LCA询问都已解,其他的LCA询问的结果必然在这个子树之外。)
- 当搜索完该节点的所有子树以后,在回溯时,把当前节点的par[x]=(x的父亲节点)(集合归并)
- 然后开始处理原先所有询问中包含了该节点的所有询问及求LCA(u,?)(体现了离线算法,对询问次序按深搜时遍历到的节点顺序进行重组)
- 在处理包含该节点的询问中,先判断当前正在处理的这条询问(求LCA(u,v))中另一个节点(v)是否也已经被遍历过,
- 若还未遍历,则暂不处理;否则LCA(u,v)= FindPar(par[v])(并查集)。(证明:因为v是在遍历到u(也就是当前的x节点)之前先遍历到了。如果有一个从当前结点(u)到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。)
- 包含该节点的所有询问全部处理完毕
<具体结合遍历和并查集的归并的顺序理解,见图>
处理LCA(3,4):因为3在遍历到4之前先被访问到,所以LCA(3,4)=FindPar(par[3])=3;(此时对LCA(4,5)的询问操作暂被跳过。
处理LCA(5,4):因为4在遍历到5之被访问,所以LCA(5,4)=Find(par[4])=2
扩展:求树上两点(u,v)间的最短距离
求树上两点间u,v的最短距离的方法:记dis[u]为u到根节点的距离
那么u到v之间的距离:ans[u][v]=dis[u]+dis[v]-2*dis[lca[u][v]](减去到根节点的公共距离的两倍);
题解:
1 #include<cstring> 2 #include<iostream> 3 using namespace std; 4 #include<cstdio> 5 #define M 201 6 #include<vector> 7 #define N 40100 8 vector <int> que[N];/*储存询问队列*/ 9 int ans[M];/*存着答案*/ 10 int n,m,u,v,k; 11 struct Edge{ 12 int v,last,w;/*边表*/ 13 }edge[N*2]; 14 int head[N],dis[N]={0}; 15 int T,t=0; 16 int father[N],ance[N];/*father[N]表示当前的处理的子树,ance代表这个点当前的祖先*/ 17 bool visit[N],root[N];/*visit判断当前的点的子树lca是否求过,root是寻找根节点*/ 18 void add_edge(int u,int v,int w) 19 { 20 ++t; 21 edge[t].v=v; 22 edge[t].w=w; 23 edge[t].last=head[u]; 24 head[u]=t; 25 } 26 void input() 27 { 28 memset(visit,false,sizeof(visit)); 29 memset(root,false,sizeof(root)); 30 memset(head,0,sizeof(head)); 31 memset(dis,0,sizeof(dis)); 32 memset(edge,0,sizeof(edge)); 33 scanf("%d%d",&n,&m); 34 for(int i=1;i<=n-1;++i) 35 { 36 scanf("%d%d%d",&u,&v,&k); 37 add_edge(u,v,k); 38 root[v]=true; 39 ance[i]=i;/*初始化*/ 40 father[i]=i; 41 } 42 for(int i=1;i<=m;++i) 43 { 44 scanf("%d%d",&u,&v); 45 que[u].push_back(i);/*因为离线算法求出结果是无法知道他的查询顺序的,但是我们要按照查询顺序输出,所以就在查询序列的偶数位存着下一位的在ans的顺序*/ 46 que[u].push_back(v); 47 que[v].push_back(i); 48 que[v].push_back(u); 49 } 50 } 51 int find(int x) 52 { 53 return (father[x]==x)?father[x]:father[x]=find(father[x]); 54 } 55 void tarjan(int k,int w) 56 { 57 ance[k]=k; 58 dis[k]=w; 59 for(int l=head[k];l;l=edge[l].last) 60 { 61 tarjan(edge[l].v,dis[k]+edge[l].w); 62 father[edge[l].v]=k;/*father存着当前点与全部的子树上的集合,同时把子树直接点的祖先设为k,所以查询一个子树上的点的区间的时候,要先用find找出代表元素,再求祖先*/ 63 ance[edge[l].v]=k; 64 } 65 visit[k]=true; 66 int size=que[k].size(); 67 for(int i=1;i<size;i+=2) 68 { 69 if(visit[que[k][i]])/*如果这条边的另一个点的lca已经求出来,那么这个点所在集合的祖先就是这两个点的最近公共祖先*/ 70 { 71 int zu=ance[find(que[k][i])]; 72 ans[que[k][i-1]]=dis[k]+dis[que[k][i]]-2*dis[zu]; 73 } 74 } 75 } 76 int main() 77 { 78 scanf("%d",&T); 79 while(T--) 80 { 81 input(); 82 for(int i=1;i<=n;++i) 83 { 84 if(!root[i])/*从根节点开始深搜*/ 85 { 86 tarjan(i,0); 87 break; 88 } 89 } 90 for(int i=1;i<=m;++i) 91 printf("%d\n",ans[i]); 92 } 93 return 0; 94 }