Nearest Common Ancestors
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 12478 | Accepted: 6652 |
Description
A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:
In the figure, each node is labeled with an integer from {1, 2,…,16}. Node 8 is the root of the tree. Node x is an ancestor of node y if node x is in the path between the root and node y. For example, node 4 is an ancestor of node 16. Node 10 is also an ancestor of node 16. As a matter of fact, nodes 8, 4, 10, and 16 are the ancestors of node 16. Remember that a node is an ancestor of itself. Nodes 8, 4, 6, and 7 are the ancestors of node 7. A node x is called a common ancestor of two different nodes y and z if node x is an ancestor of node y and an ancestor of node z. Thus, nodes 8 and 4 are the common ancestors of nodes 16 and 7. A node x is called the nearest common ancestor of nodes y and z if x is a common ancestor of y and z and nearest to y and z among their common ancestors. Hence, the nearest common ancestor of nodes 16 and 7 is node 4. Node 4 is nearer to nodes 16 and 7 than node 8 is.
For other examples, the nearest common ancestor of nodes 2 and 3 is node 10, the nearest common ancestor of nodes 6 and 13 is node 8, and the nearest common ancestor of nodes 4 and 12 is node 4. In the last example, if y is an ancestor of z, then the nearest common ancestor of y and z is y.
Write a program that finds the nearest common ancestor of two distinct nodes in a tree.
Input
The input consists of T test cases. The number of test cases (T) is given in the first line of the input file. Each test case starts with a line containing an integer N , the number of nodes in a tree, 2<=N<=10,000. The nodes are labeled with integers 1, 2,…, N. Each of the next N -1 lines contains a pair of integers that represent an edge –the first integer is the parent node of the second integer. Note that a tree with N nodes has exactly N – 1 edges. The last line of each test case contains two distinct integers whose nearest common ancestor is to be computed.
Output
Print exactly one line for each test case. The line should contain the integer that is the nearest common ancestor.
Sample Input
2 16 1 14 8 5 10 16 5 9 4 6 8 4 4 10 1 13 6 15 10 11 6 7 10 2 16 3 8 1 16 12 16 7 5 2 3 3 4 3 1 1 5 3 5
Sample Output
4 3
Source
Taejon 2002
很简单的LCA题目。
总结和检测下模板之用。
LCA有离线算法和在线算法。
程序如下。
在线算法:
/* POJ 1330 求最近公共祖先(LCA) 在线算法 DFS+ST描述 对于本题只有一个查询,不适合用在线算法,用离线算法效率更高 */ #include<stdio.h> #include<iostream> #include<math.h> #include<string.h> #include<algorithm> using namespace std; const int MAXN=10010; int rmq[2*MAXN];//建立RMQ的数组 //*************************** //ST算法,里面含有初始化init(n)和query(s,t)函数 //点的编号从1开始,1-n.返回最小值的下标 //*************************** struct ST { int mm[2*MAXN];//mm[i]表示i的最高位,mm[1]=0,mm[2]=1,mm[3]=1,mm[4]=2 int dp[MAXN*2][20]; void init(int n) { mm[0]=-1; for(int i=1;i<=n;i++) { mm[i]=((i&(i-1))==0?mm[i-1]+1:mm[i-1]); dp[i][0]=i; } for(int j=1;j<=mm[n];j++) for(int i=1;i+(1<<j)-1<=n;i++) dp[i][j]=rmq[dp[i][j-1]]<rmq[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1]; } int query(int a,int b)//查询a到b间最小值的下标 { if(a>b)swap(a,b); int k=mm[b-a+1]; return rmq[dp[a][k]]<rmq[dp[b-(1<<k)+1][k]]?dp[a][k]:dp[b-(1<<k)+1][k]; } }; //边的结构体定义 struct Node { int to,next; }; /* ****************************************** LCA转化为RMQ的问题 MAXN为最大结点数。ST的数组 和 F,edge要设置为2*MAXN F是欧拉序列,rmq是深度序列,P是某点在F中第一次出现的下标 *********************************************/ struct LCA2RMQ { int n;//结点个数 Node edge[2*MAXN];//树的边,因为是建无向边,所以是两倍 int tol;//边的计数 int head[MAXN];//头结点 bool vis[MAXN];//访问标记 int F[2*MAXN];//F是欧拉序列,就是DFS遍历的顺序 int P[MAXN];//某点在F中第一次出现的位置 int cnt; ST st; void init(int n)//n为所以点的总个数,可以从0开始,也可以从1开始 { this->n=n; tol=0; memset(head,-1,sizeof(head)); } void addedge(int a,int b)//加边 { edge[tol].to=b; edge[tol].next=head[a]; head[a]=tol++; edge[tol].to=a; edge[tol].next=head[b]; head[b]=tol++; } int query(int a,int b)//传入两个节点,返回他们的LCA编号 { return F[st.query(P[a],P[b])]; } void dfs(int a,int lev) { vis[a]=true; ++cnt;//先加,保证F序列和rmq序列从1开始 F[cnt]=a;//欧拉序列,编号从1开始,共2*n-1个元素 rmq[cnt]=lev;//rmq数组是深度序列 P[a]=cnt; for(int i=head[a];i!=-1;i=edge[i].next) { int v=edge[i].to; if(vis[v])continue; dfs(v,lev+1); ++cnt; F[cnt]=a; rmq[cnt]=lev; } } void solve(int root) { memset(vis,false,sizeof(vis)); cnt=0; dfs(root,0); st.init(2*n-1); } }lca; bool flag[MAXN]; int main() { int T; int N; int u,v; scanf("%d",&T); while(T--) { scanf("%d",&N); memset(flag,false,sizeof(flag)); lca.init(N); for(int i=1;i<N;i++) { scanf("%d%d",&u,&v); lca.addedge(u,v); flag[v]=true; } int root; for(int i=1;i<=N;i++) if(!flag[i]) { root=i; break; } lca.solve(root); scanf("%d%d",&u,&v); printf("%d\n",lca.query(u,v)); } return 0; }
离线算法。
/* POJ 1330 离线算法 */ #include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> #include<math.h> #include<vector> using namespace std; const int MAXN=10010; int F[MAXN];//并查集 int r[MAXN];//并查集中集合的个数 bool vis[MAXN];//访问标记 int ancestor[MAXN];//祖先 struct Node { int to,next; }edge[MAXN*2]; int head[MAXN]; int tol; void addedge(int a,int b) { edge[tol].to=b; edge[tol].next=head[a]; head[a]=tol++; edge[tol].to=a; edge[tol].next=head[b]; head[b]=tol++; } struct Query { int q,next; int index;//查询编号 }query[MAXN*2];//查询数 int answer[MAXN*2];//查询结果 int cnt; int h[MAXN]; int tt; int Q;//查询个数 void add_query(int a,int b,int i) { query[tt].q=b; query[tt].next=h[a]; query[tt].index=i; h[a]=tt++; query[tt].q=a; query[tt].next=h[b]; query[tt].index=i; h[b]=tt++; } void init(int n) { for(int i=1;i<=n;i++) { F[i]=-1; r[i]=1; vis[i]=false; ancestor[i]=0; tol=0; tt=0; cnt=0;//已经查询到的个数 } memset(head,-1,sizeof(head)); memset(h,-1,sizeof(h)); } int find(int x) { if(F[x]==-1)return x; return F[x]=find(F[x]); } void Union(int x,int y)//合并 { int t1=find(x); int t2=find(y); if(t1!=t2) { if(r[t1]<=r[t2]) { F[t1]=t2; r[t2]+=r[t1]; } else { F[t2]=t1; r[t1]+=r[t2]; } } } void LCA(int u) { //if(cnt>=Q)return;//不要加这个 ancestor[u]=u; vis[u]=true;//这个一定要放在前面 for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(vis[v])continue; LCA(v); Union(u,v); ancestor[find(u)]=u; } for(int i=h[u];i!=-1;i=query[i].next) { int v=query[i].q; if(vis[v]) { answer[query[i].index]=ancestor[find(v)]; cnt++;//已经找到的答案数 } } } bool flag[MAXN]; int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; int N; int u,v; scanf("%d",&T); while(T--) { scanf("%d",&N); init(N); memset(flag,false,sizeof(flag)); for(int i=1;i<N;i++) { scanf("%d%d",&u,&v); flag[v]=true; addedge(u,v); } Q=1;//查询只有一组 scanf("%d%d",&u,&v); add_query(u,v,0);//增加一组查询 int root; for(int i=1;i<=N;i++) if(!flag[i]) { root=i; break; } LCA(root); for(int i=0;i<Q;i++)//输出所有答案 printf("%d\n",answer[i]); } return 0; }