LCA
最近公共祖先
LCA
Tarjan
树剖
最简单的\(LCA\)就是利用倍增的思想,\(f[i][j]\)表示从\(i\)号节点往上跳\(2^j\)个点到哪了。
先将两个点跳到同一高度,然后一块往上跳,最后得到的节点的父亲就是答案。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,m,s,k,head[500002],d[500002],p[500002][21];
struct node{
int v,next;
}e[2*500002];
void add(int a,int b){
e[k].v=b,e[k].next=head[a],head[a]=k++;
}
void dfs(int u,int fa){
d[u]=d[fa]+1,p[u][0]=fa;
for(int i=1;(1<<i)<=d[u];i++) p[u][i]=p[p[u][i-1]][i-1];
for(int i=head[u],v;v=e[i].v,i!=-1;i=e[i].next)
if(v!=fa) dfs(v,u);
}
int lca(int a,int b){
if(d[a]>d[b]) swap(a,b);
for(int i=20;i>=0;i--)
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;
else a=p[a][i],b=p[b][i];
return p[a][0];
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d%d",&n,&m,&s);
int a,b;
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;
}
\(Tarjan~LCA\)
这是一种离线算法。
思想就是先将所有边和所有问题存下来,先\(dfs\),将子节点与父亲节点合并,返回的时候顺便记录一下答案。因为每棵子树连续遍历,所以返回的时候找到的爸爸就是两点的最近公共祖先。
#include<iostream>
#include<cstdlib>
#include<cctype>
using namespace std;
int fa[500010],head[500010],qhead[500010],que[500010],cnt,x,y,n,m,s;
struct Edge{//树
int next,to;
}edge[1000010];
struct qEdge{//问题
int next,to,ans=0;
}q[1000010];
void add(int x,int y){//树
edge[++cnt].to=y,edge[cnt].next=head[x],head[x]=cnt;
}
void qadd(int x,int y,int k){//问题
q[k].to=y,q[k].next=qhead[x],qhead[x]=k;
}
//并查集
int find(int x){
return fa[x]!=x? fa[x]=find(fa[x]):fa[x];
}
void unionn(int x,int y){
x=find(x),y=find(y);
fa[y]=x;
}
void tarjan(int x){
que[x]=1;
for(int i=head[x],to;to=edge[i].to,i;i=edge[i].next)
if(!que[to]){
tarjan(to);
unionn(x,to);//将子节点向父节点合并
}
for(int i=qhead[x],to;i,to=q[i].to;i=q[i].next)
if(que[to]==2){//记录答案
q[i].ans=find(to);
if(i%2) q[i+1].ans=q[i].ans;//鬼畜写法
else q[i-1].ans=q[i].ans;
}
que[x]=2;//表示搜完了
}
int main(){
cin>>n>>m>>s;
for(int i=1;i<n;++i){
cin>>x>>y;
add(x,y);add(y,x);
fa[i]=i;
}
fa[n]=n;
for(int i=1;i<=m;++i){
cin>>x>>y;
qadd(x,y,i*2-1);qadd(y,x,i*2);
}
tarjan(s);
for(int i=1;i<=n;++i)
cout<<q[i*2].ans;
return 0;
}
第三种好写不长也同样很好理解的方式是树剖。比前面两种方法的代码都短。
这里简单提一句吧。就是两边dfs记录每条链的top,只要两个点不在一条连上,就往链头的父亲上跳,直到跳到同一条链上,这是谁的深度小谁就是答案。感觉比前两个要快
#include <iostream>
#include <cstdio>
using namespace std;
long long read() {
long long x = 0; int f = 0; char c = getchar();
while (c < '0' || c > '9') f |= c == '-', c = getchar();
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
int n, m, s;
int hd[500005], cnt, son[500005], top[500005],w[500005], dep[500005], f[500005];
struct szh {
int to, nxt;
}a[1000005];
void add(int x, int y) {
a[++cnt].to = y, a[cnt].nxt = hd[x], hd[x] = cnt;
}
void dfs(int u, int F) {
dep[u] = dep[F] + 1; w[u] = 1; f[u] = F;
for (int i = hd[u], v; v = a[i].to, i; i = a[i].nxt) {
if (v == F) continue;
dfs(v, u);
w[u] += w[v];
if (!son[u] || w[v] > w[son[u]]) son[u] = v;
}
}
void dfs(int u, int tp, int F) {
top[u] = tp;
if (son[u]) dfs(son[u], tp, u);
for (int i = hd[u], v; v = a[i].to, i; i = a[i].nxt) {
if (v == F || v == son[u]) continue;
dfs(v, v, u);
}
}
int main() {
n = read(); m = read(); s = read();
for (int i = 1, x, y; i < n; ++i) {
x = read(); y = read(); add(x, y); add(y, x);
}
dfs(s, 0); dfs(s, s, 0);
int a, b;
while (m--) {
a = read(); b = read();
while (top[a] != top[b])
if (dep[top[a]] >= dep[top[b]]) a = f[top[a]];
else b = f[top[b]];
printf("%d\n", dep[a] > dep[b] ? b : a);
}
return 0;
}
欢迎指正评论O(∩_∩)O~~