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
",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
", dep[a] > dep[b] ? b : a);
  }
  return 0;
}

欢迎指正评论O(∩_∩)O~~

原文地址:https://www.cnblogs.com/kylinbalck/p/9878895.html