LCA一共学习了三种方法,分别是
1.倍增
2.RMQ
3.树链剖分
首先是倍增,先是预处理,倍增需要处理出f[i][x] 表示x点向上走2的i次方所到的点,先处理所有f[0][x],f[0][x] = father[x],因为x点向上走2的0次方(也就是1)就是它的父亲,然后再推到f[i][x] = (f[i - 1][f[i - 1][x]])表示先从x点向上走2的i - 1次方,然后再向上走2的i - 1次方,预处理完毕。
再是求LCA,先让深度较大的那个点转换成和深度较浅的点的深度相同,然后就会有两种情况
1.如果两个点发现在同一个点上,就代表其中一个点就是他们的公共祖先了,直接输出答案就好
2.如果两个点不在一个点上,我们采用逐步逼近法,因为LCA上面的点肯定都是相同的,从大到小枚举2的幂,如果不相同说明还在LCA的下面,就继续向上走,一直到最后绝对只差1。
两个情况特判一下就好。
预处理代码
int k = 17;
for(int i = 1;i <= n;i ++)
f[0][i] = father[i];
for(int i = 1;i <= k;i ++)
{
for(int j = 1;j <= n;j ++)
f[i][j] = f[i - 1][f[i - 1][j]];
}
靠近深度代码
int k = 17;
void get(int x,int y)
{
if(d[x] < d[y]) // 保证x 的深度大于 y
swap(x,y);
for(int i = k;i >= 1;i --)
if(d[f[i][x]] >= d[y])
x = f[i][x];
}
寻找LCA代码
int k = 17;
void get(int x,int y)
{
for(int i = k;i >= 1;i --)
if(f[i][x] != f[i][y])
{
x = f[i][x];
y = f[i][y];
}
}
然后是RMQ求LCA
我们先认识一下原理,先跑一遍DFS,记录第一次DFS到的点的位置和深度,然后每次查询,跑一下RMQ,求出区间最浅,就是公共祖先
详见代码
#include
#include
#include
using namespace std;
const int M = 500000;
struct Edge{
int fr;
int to;
int next;
}edge[M * 2 + 5];
struct finkery{
int d;
int b;
};
int head[M * 2 + 5];
int mark[M * 2 + 5];
int num;
int num_1;
int pos[M * 2+ 5];
finkery q[M * 2 + 5];
int mark_1[M * 2 + 5];
finkery dp[20][M * 2 + 5];
int log1[M * 2+ 5];
finkery min(finkery x,finkery y)
{
if(x.d < y.d)
return x;
return y;
}
void add_edge(int x,int y)
{
edge[++ num].fr = x;
edge[num].to = y;
edge[num].next = head[x];
head[x] = num;
}
void dfs(int x,int y)
{
if(! mark[x])
{
pos[x] = ++ num_1;
q[num_1].b = x;
q[num_1].d = y;
mark[x] = 1;
}
for(int i = head[x];i;i = edge[i].next)
{
if(mark_1[i] || mark[edge[i].to]) continue;
mark_1[i] = 1;
dfs(edge[i].to,y + 1);
q[++ num_1].b = x;
q[num_1].d = y;
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i = 1;i <= n - 1;i ++)
{
int fr,to;
scanf("%d%d",&fr,&to);
add_edge(fr,to);
add_edge(to,fr);
}
dfs(s,1);
/*for(int i = 1;i <= num_1;i ++)
printf("%d ",q[i].b);
for(int i = 1;i <= n;i ++)
printf("%d ",pos[i]);*/
for(int i = 1;i <= num_1;i ++)
dp[0][i].d = q[i].d,dp[0][i].b = q[i].b;
/* for(int i = 1;i <= num_1;i ++)
printf("%d ",q[i].d);*/
for(int k = 1;(1 << k) <= num_1;k ++)
for(int i = 1;i <= num_1;i ++)
if(i + (1 << (k - 1)) >= num_1)
dp[k][i] = dp[k - 1][i];
else
dp[k][i] = min(dp[k - 1][i],dp[k - 1][i + (1 << (k - 1))]);
/* printf("\n");
for(int i = 1;i <= num_1;i ++)
printf("%d ",q[i].b);
printf("\n");
for(int k = 0;(1 << k) <= num_1;k ++)
{
for(int i = 1;i <= num_1;i ++)
printf("%d ",dp[k][i].d);
printf("\n");
for(int i = 1;i <= num_1;i ++)
printf("%d ",dp[k][i].b);
printf("\n");
}*/
for(int i = 2;i <= num_1;i ++)
log1[i] = log1[i >> 1] + 1;
for(int i = 1;i <= m;i ++)
{
int l,r;
scanf("%d%d",&l,&r);
r = pos[r];
l = pos[l];
if(r < l)
swap(r,l);
// printf("%d %d\n",l,r);
int mid = log1[r - l + 1];
// printf("%d\n",mid);
//printf("%d ",min(dp[mid][l].d,dp[mid][r - (1 << mid) + 1].d));
if(dp[mid][l].d < dp[mid][r - (1 << mid) + 1].d)
printf("%d\n",dp[mid][l].b);
else
printf("%d\n",dp[mid][r - (1 << mid) + 1].b);
//printf("\n");
}
return 0;
}
第三种是树链剖分,我们可以设一个点有一个重儿子和若干个轻儿子,重儿子就是含有点较多的儿子,用两次DFS,第一次维护深度,父亲,子树数量,第二题维护top,每个点标记上父亲和深度,和自己所在重链的top,如果两个点的top是同一个,就输出深度较浅的那个,否则top较低的那个跳到top的父亲
详见代码
// luogu-judger-enable-o2
#include
#include
#include
using namespace std;
const int M = 500000;
struct Edge{
int fr;
int to;
int next;
}edge[M * 2 + 5];
int head[M * 2 + 5];
int f[M + 5];
int size[M + 5];
int top[M + 5];
int son[M + 5];
int mark[M * 2 + 5];
int d[M + 5];
int mark_1[M];
int num;
void add_edge(int x,int y)
{
num ++;
edge[num].fr = x;
edge[num].to = y;
edge[num].next = head[x];
head[x] = num;
}
void dfs(int x,int dnow)
{
size[x] = 1;
d[x] = dnow;
int maxn = 0;
for(int i = head[x];i;i = edge[i].next)
{
if(mark[i] || mark_1[edge[i].to]) continue;
mark[i] = 1;
mark_1[edge[i].to] = 1;
dfs(edge[i].to,dnow + 1);
f[edge[i].to] = x;
if(size[edge[i].to] > maxn)
{
maxn = size[edge[i].to];
son[x] = edge[i].to;
}
size[x] += size[edge[i].to];
mark[i] = 0;
mark_1[edge[i].to] = 0;
}
}
void dfs2(int x,int top_1)
{
top[x] = top_1;
for(int i = head[x];i;i = edge[i].next)
{
if(mark[i] || mark_1[edge[i].to])
continue;
mark[i] = 1;
mark_1[edge[i].to] = 1;
if(edge[i].to == son[x]) dfs2(edge[i].to,top_1);
else dfs2(edge[i].to,edge[i].to);
mark[i] = 0;
mark_1[edge[i].to] = 0;
}
}
void print(int l,int r)
{
while(top[l] != top[r])
{
if(d[top[l]] < d[top[r]])
r = f[top[r]];
else
l = f[top[l]];
}
printf("%d\n",d[l] < d[r] ? l : r);
return;
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i = 1;i <= n - 1;i ++)
{
int fr;
int to;
scanf("%d%d",&fr,&to);
add_edge(fr,to);
add_edge(to,fr);
}
mark_1[s] = 1;
dfs(s,0);
mark_1[s] = 1;
dfs2(s,s);
/*for(int i = 1;i <= n;i ++)
printf("%d ",f[i]);*/
for(int i = 1;i <= m;i ++)
{
int l,r;
scanf("%d%d",&l,&r);
if(top[l] == top[r])
printf("%d\n",d[l] < d[r] ? l : r);
else
print(l,r);
}
}