最近公共祖先(LCA)(链式前向星+倍增法)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的最近公共祖先。
输出格式
输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1
5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5
输出 #1
4 4 1 4 4
说明/提示
对于 30\%30% 的数据,N\leq 10N≤10
对于 70\%70% 的数据,N\leq 10000N≤10000
对于 100\%100% 的数据,N\leq 500000N≤500000,
样例说明:
该树结构如下:
第一次询问:2, 42,4 的最近公共祖先,故为 44。
第二次询问:3, 23,2 的最近公共祖先,故为 44。
第三次询问:3, 53,5 的最近公共祖先,故为 11。
第四次询问:1, 21,2 的最近公共祖先,故为 44。
第五次询问:4, 54,5 的最近公共祖先,故为 44。
故输出依次为 4, 4, 1, 4, 44,4,1,4,4。
#include <bits/stdc++.h> using namespace std; //利用倍增法,找到最近公共祖先 //链式前向星的存储方式 /* 缺点: 1.重边不好处理 2.不能通过两点马上确定对应的边 */ int n,m,s; const int maxn = 500000+5; int k;//边号 struct edge{ int v;//边对应的另一个顶点 int next;//下一条边好,如果没有的话,为-1 }E[maxn*2];//无向图,所以边要存两遍 int head[maxn];//head[i],记录顶点i的第一条边 int d[maxn],p[maxn][21]; //d[i]存的是i节点对应的深度,p[i][j]对从i位置向上走2的j次方所能到达的位置 //新增的边成为顶点u所对应的第一条边 void add(int u,int v){ E[k].v = v; E[k].next = head[u];//头插法,将该边插在最前面 head[u] = k++; } void dfs(int u,int f){ d[u] = d[f]+1; p[u][0] = f; //边界确保最最多跳两次即达到根节点边界处 for(int i=1;(1<<i)<=d[u];i++){ p[u][i] = p[p[u][i-1]][i-1]; //相当于u的2的i次方祖先的位置 == u的2的i-1祖先的位置再往上2的i-1 //2^i = 2^(i-1)+2^(i-1)=2*2^(i-1) } //从上到下递归 for(int i=head[u];i!=-1;i=E[i].next){ int v = E[i].v; if(v!=f){ dfs(v,u); //保证递归的时候不陷入相应的死循环 } } } int lca(int a,int b){ if(d[a]>d[b]){ swap(a,b);//保证a的深度小 } //先将a,b处理成同一高度 for(int i=20;i>=0;i--){ //b先往上跳 //先从跨度大的开始跳,然后确保二者高度最终达到一致的标准 if(d[a]<=d[b]-(1<<i)){ b = p[b][i]; } } //如果移动到同一层次后,重合,则返回相应的答案 if(a==b) return a; for(int i=20;i>=0;i--){ if(p[a][i]==p[b][i]) continue;//跳度大的地方均相同 //跳到小一级的地方 a = p[a][i],b = p[b][i]; } return p[a][0]; } int main(){ memset(head,-1,sizeof(head)); int a,b; scanf("%d%d%d",&n,&m,&s); for(int i=1;i<n;i++){ scanf("%d%d",&a,&b); add(a,b); add(b,a); } dfs(s,0);//从根节点出发,开始进行一遍预处理 for(int i=1;i<=m;i++){ scanf("%d%d",&a,&b); printf("%d\n",lca(a,b)); } return 0; }