模板题详见洛谷P3379
什么是lca
l c a lca lca,即 最近公共祖先。具体解释请自行百度~~~(我太弱了)
lca怎么求
1.最简单的方法则是“暴力”。将深度大的节点向上走到与深度小的节点的深度相同;之后两个节点一起向上跳,知道它们所在的节点相同。
但是这种做法的时间复杂度太高了
Q
A
Q
QAQ
QAQ ,所以并不适合。
2.所以考虑利用倍增求lca。先开一个
f
[
]
[
]
f[ ] [ ]
f[][] 数组,
f
[
i
]
[
j
]
f[ i ][ j ]
f[i][j] 表示节点i向上跳
2
j
2^j
2j个单位后的节点,之后再“暴力”求解,这样复杂度就少很多了!
流程
1.常规建边(这个不用解释吧)
void add(int x,int y)
{
tot++;
tr[tot].v=y;
tr[tot].next=head[x];
head[x]=tot;
}//建边
2.预处理
d
e
p
[
i
]
dep[ i ]
dep[i]表示 节点i的深度,
f
a
[
i
]
fa[ i ]
fa[i],表示节点i的父节点(其实并没有什么用),重点就是求
f
[
i
]
[
j
]
f[ i ][ j ]
f[i][j]。可以证明,节点i向上跳
2
j
2^j
2j个节点(即
f
[
i
]
[
j
]
f[i][j]
f[i][j])就是节点i向上跳 2j-1个节点后的节点(即
f
[
i
]
[
j
−
1
]
f[i][j-1]
f[i][j−1])再向上跳2j-1个节点,因为2j-1+2j-1=2j。(原谅我语文不好
Q
A
Q
QAQ
QAQ,其实可以自己推一下)
void ready(int u,int v)//v是u的父亲
{
dep[u]=dep[v]+1;
fa[u]=v;
//f[u][0]=v;
for(int i=0;i<=20;i++)
f[u][i+1]=f[f[u][i]][i];//预处理倍增数组
for(int t=head[u];t;t=tr[t].next)
{
int nw=tr[t].v;
if(nw==v)continue;
f[nw][0]=u;
ready(nw,u);//dfs
}
}//预处理
3.接下来就是重点——求
l
c
a
lca
lca了
假设要求
x
x
x和
y
y
y节点的
l
c
a
lca
lca,且
x
x
x比
y
y
y深,那么我们就利用倍增,先让
x
x
x向上跳,直到与
y
y
y的深度相同。若此时
x
x
x等于
y
y
y节点,则直接返回
x
x
x或
y
y
y节点。若不相同,算法继续。
接下来让 x x x和 y y y一起向上跳,方法还是倍增。但需注意的是不能让 x x x和 y y y跳到 l c a lca lca以上的节点,也就是 f [ x ] [ i ] ! = f [ y ] [ i ] f[x][i]!=f[y][i] f[x][i]!=f[y][i]。最后 x x x和 y y y会分别是 l c a lca lca的子节点,直接返回 x x x或 y y y的父节点(即 f [ x ] [ 0 ] f[x][0] f[x][0]或 f [ x ] [ 0 ] f[x][0] f[x][0])即可。
int lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--)
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];//保证x一直比y深或与y深度相同
if(x==y)return x;//有答案则直接得出答案
}//让x与y的深度相同
for(int i=20;i>=0;i--)
{
if(f[x][i]!=f[y][i])//不同说明没有超过lca,跳
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];//最终是在lca的子节点停下来
}//先跳到同一深度,再同时向上跳
完整代码
#include<cstdio>
#include<algorithm>
#define maxn 500005
using namespace std;
struct tree
{
int v,next;
}tr[maxn*4];
int n,m,s,tot,head[maxn];
int f[maxn][25],fa[maxn],dep[maxn],ans[maxn];//f[i][j]表示点i跳2^j的节点,dep表示节点的深度
void add(int x,int y)
{
tot++;
tr[tot].v=y;
tr[tot].next=head[x];
head[x]=tot;
}//建边
void ready(int u,int v)//v是u的父亲
{
dep[u]=dep[v]+1;
fa[u]=v;
//f[u][0]=v;
for(int i=0;i<=20;i++)
f[u][i+1]=f[f[u][i]][i];//预处理倍增数组
for(int t=head[u];t;t=tr[t].next)
{
int nw=tr[t].v;
if(nw==v)continue;
f[nw][0]=u;
ready(nw,u);//dfs
}
}//预处理
int lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--)
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];//保证x一直比y深或与y深度相同
if(x==y)return x;//有答案则直接得出答案
}//让x与y的深度相同
for(int i=20;i>=0;i--)
{
if(f[x][i]!=f[y][i])//不同说明没有超过lca,跳
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];//最终是在lca的子节点停下来
}//先跳到同一深度,再同时向上跳
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}//建边
ready(s,0);//预处理
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
ans[i]=lca(x,y);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
终于写完了,我还是太弱了。