LCA复习笔记
chinaxjh
2019-11-13 20:25:03
# Part 1
## 简介
求树上最近公共祖先的算法
可以在$O(nlog_n)$时间复杂度的预处理后,在以$O(log_n)$的时间复杂度回答每一次询问节点$x$,$y$的$LCA$
# Part 2
## 模板
[题目](https://www.luogu.org/problem/P3379)
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
int n,m,s,i,len,x,y,a[N],nxt[N],las[N],yu[N][21],dep[N],fa[N];
void add(int x,int y)
{
len++;
a[len]=y;
nxt[len]=las[x];
las[x]=len;
}
void yusc(int x,int fa)
{
int i;
dep[x]=dep[fa]+1;
for (i=0;i<=19;i++)
yu[x][i+1]=yu[yu[x][i]][i];
for (i=las[x];i;i=nxt[i])
if (fa!=a[i])
{
yu[a[i]][0]=x;
yusc(a[i],x);
}
}
int LCA(int x,int y)
{
int i;
if (dep[x]<dep[y]) swap(x,y);
for (i=20;i>=0;i--)
{
if (dep[yu[x][i]]>=dep[y]) x=yu[x][i];
if (x==y) return x;
}
for (i=20;i>=0;i--)
{
if (yu[x][i]!=yu[y][i])
{
x=yu[x][i];
y=yu[y][i];
}
}
return yu[x][0];
}
int main()
{
cin>>n>>m>>s;
for (i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
yusc(s,0);
for (i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
}
```
# Part 3
## 例题
#### 货车运输
$Kruskal$生成最大树,然后$LCA$求解两点之间路径最小值
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,m,s,i,len,x,y,q,a[N],b[N],nxt[N],las[N],yu[N][21],ans[N][21],dep[N],fa[N];
bool visit[N];
struct node
{
int x,y,z;
}k[N];
bool cmp(node aa,node bb)
{
return aa.z>bb.z;
}
int gfa(int x)
{
if (x==fa[x]) return x;
fa[x]=gfa(fa[x]);
return fa[x];
}
void add(int x,int y,int z)
{
len++;
a[len]=y;
b[len]=z;
nxt[len]=las[x];
las[x]=len;
}
void kruskal()
{
int i,xx,yy;
for (i=1;i<=m;i++)
{
xx=gfa(k[i].x);
yy=gfa(k[i].y);
if (xx!=yy)
{
fa[yy]=xx;
add(k[i].x,k[i].y,k[i].z);
add(k[i].y,k[i].x,k[i].z);
}
}
}
void yusc(int x,int fa)
{
int i;
visit[x]=true;
dep[x]=dep[fa]+1;
for (i=0;i<=19;i++)
yu[x][i+1]=yu[yu[x][i]][i],
ans[x][i+1]=min(ans[x][i],ans[yu[x][i]][i]);
for (i=las[x];i;i=nxt[i])
if (fa!=a[i])
{
yu[a[i]][0]=x;
ans[a[i]][0]=b[i];
yusc(a[i],x);
}
}
int LCA(int x,int y)
{
int i,anss;
anss=1000000000;
if (dep[x]<dep[y]) swap(x,y);
for (i=20;i>=0;i--)
{
if (dep[yu[x][i]]>=dep[y])
{
anss=min(anss,ans[x][i]);
x=yu[x][i];
}
if (x==y) return anss;
}
for (i=20;i>=0;i--)
{
if (yu[x][i]!=yu[y][i])
{
anss=min(anss,ans[x][i]);
anss=min(anss,ans[y][i]);
x=yu[x][i];
y=yu[y][i];
}
}
return min(min(ans[x][0],ans[y][0]),anss);
}
int main()
{
cin>>n>>m;
for (i=1;i<=n;i++) fa[i]=i;
for (i=1;i<=m;i++)
scanf("%d%d%d",&k[i].x,&k[i].y,&k[i].z);
sort(k+1,k+1+m,cmp);
kruskal();
for (i=1;i<=n;i++)
if (!visit[i])
yusc(i,0);
cin>>q;
for (i=1;i<=q;i++)
{
scanf("%d%d",&x,&y);
int w=LCA(x,y);
if (w==0) printf("-1\n");
else printf("%d\n",w);
}
}
```
#### 仓鼠找sugar
[题目](https://www.luogu.org/problem/P3398)
题意很明确,这里涉及到一个重要的知识点,如何判定两个树上的路径相交。
如果两条路径相交,那么一定有一条路径的$LCA$在另一条路径上
而判断一个节点$x$,是否在路径$s-t$上需要满足如下几个条件
- $deep[x]>=deep[LCA(s,t)]$
- $LCA(s,x)=x$或$LCA(t,x)=x;$
摘自:[沧澜](https://www.luogu.org/user/21719)大佬的博客
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
int n,m,s,i,len,x,y,xx,yy,t,k,a[N],nxt[N],las[N],yu[N][21],dep[N],fa[N];
void add(int x,int y)
{
len++;
a[len]=y;
nxt[len]=las[x];
las[x]=len;
}
void yusc(int x,int fa)
{
int i;
dep[x]=dep[fa]+1;
for (i=0;i<=19;i++)
yu[x][i+1]=yu[yu[x][i]][i];
for (i=las[x];i;i=nxt[i])
if (fa!=a[i])
{
yu[a[i]][0]=x;
yusc(a[i],x);
}
}
int LCA(int x,int y)
{
int i;
if (dep[x]<dep[y]) swap(x,y);
for (i=20;i>=0;i--)
{
if (dep[yu[x][i]]>=dep[y]) x=yu[x][i];
if (x==y) return x;
}
for (i=20;i>=0;i--)
{
if (yu[x][i]!=yu[y][i])
{
x=yu[x][i];
y=yu[y][i];
}
}
return yu[x][0];
}
int main()
{
cin>>n>>m;
for (i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
yusc(1,0);
for (i=1;i<=m;i++)
{
scanf("%d%d%d%d",&x,&y,&xx,&yy);
t=LCA(x,y);
k=LCA(xx,yy);
if (dep[t]>=dep[k])
{
if (LCA(t,xx)==t||LCA(t,yy)==t) puts("Y");
else puts("N");
} else {
if (LCA(k,x)==k||LCA(k,y)==k) puts("Y");
else puts("N");
}
}
}
```