超详细的LCA 两种方法(模板!)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;

const int maxn = 5e5 + 10;
struct node{
    
    
	int v, nex;
}edge[maxn << 1];
struct Node{
    
    
	int v, id;
};
int father[maxn], head[maxn], ans[maxn];
int n, m, r, cnt;
vector<Node> q[maxn];
bool vis[maxn << 1];

void init()
{
    
    
	mem(head, -1);
	for(int i = 0; i < maxn; i++)
		father[i] = i;
}

void addedge(int u, int v)//链式向前星存边
{
    
    
	edge[cnt].v = v;
	edge[cnt].nex = head[u];
	head[u] = cnt++;
}

int find(int x)
{
    
    
	int cur = x;
	while(x != father[x])
	{
    
    
		x = father[x];
	}
	while(cur != x)
	{
    
    
		int z = cur;
		cur = father[z];
		father[z] = x;
	}
	return x;
}

void Union(int x, int fa)
{
    
    
	int fx = find(x);
	int fy = find(fa);
	if(fx != fy)
		father[fx] = fy;
}

void tarjan(int u)
{
    
    
	vis[u] = true;//标记 
	for(int i = head[u]; i != -1; i = edge[i].nex)//遍历当前节点的儿子
	{
    
    
		int v = edge[i].v;
		if(vis[v])	continue;//若访问过, 跳过,主要是为了防止查找其父亲节点,若是单向边可以省去 
		tarjan(v);
		Union(v, u);//将v的并到u的祖先里 
	}
	vector<Node>::iterator it = q[u].begin();
	for(; it != q[u].end(); it++)//需要查找的边里有没有以u为起点 
	{
    
    
		if(vis[(*it).v])
		{
    
    
			ans[(*it).id] = find((*it).v);//若访问过,则该组答案就是此时v的祖先 
		}
	}
}

int main()
{
    
    
	init();
//	cin >> n >> m >> r;
	scanf("%d%d%d", &n, &m, &r);
	int x, y;
	for(int i = 0; i < n - 1; i++)
	{
    
    
//		cin >> x >> y;
		scanf("%d%d", &x, &y);
		addedge(x, y);
		addedge(y, x);
	}
	for(int i = 0; i < m; i++)
	{
    
    
//		cin >> x >> y;
		scanf("%d%d", &x, &y);
		Node cur;
		cur.v = y;
		cur.id = i;
		q[x].push_back(cur);
		cur.v = x;
		q[y].push_back(cur);
	}
	tarjan(r);
	for(int i = 0; i < m; i++)
		printf("%d\n", ans[i]);
//		cout << ans[i] << endl;
	return 0;
}

#include <bits/stdc++.h>
using namespace std;
int n,m,s,x,y,tot=0;
const int N=500005,M=1000005;//N存储节点总数,M存储边的总数
int head[N],edge[M],Next[M];
int deep[N],fa[N][22],lg[N];
//deep[i]是i号节点的深度
//lg是log数组
void add(int x,int y)//链式前项星加边
{
    
    
	edge[++tot]=y;//存储节点
	Next[tot]=head[x];//链表
	head[x]=tot;//标记节点位置
	return ;
}
void dfs(int x,int y)
{
    
    
	deep[x]=deep[y]+1;//x是y的儿子节点,所以要+1
	fa[x][0]=y;//fa[x][0]表示x的父亲节点,而y是x的父亲节点.
	for(int i=1; (1<<i)<=deep[x]; i++) //2^i<=deep[x]表示不能跳出去了,最多跳到根节点上面
		fa[x][i]=fa[fa[x][i-1]][i-1];//状态转移 2^i=2^(i-1)+2^(i-1)
	for(int i=head[x]; i; i=Next[i]) //遍历所有的出边
		if(edge[i]!=y)//因为是无向图,所以要避免回到父亲节点上面去了
			dfs(edge[i],x);//访问儿子节点,并且标记自己是父亲节点
	return ;//返回
}
int LCA(int x,int y)
{
    
    
	if(deep[x]<deep[y])//强制要求x节点是在下方的节点
		swap(x,y);//交换,维持性质
	while(deep[x]>deep[y])//当我们还没有使得节点同样深度
		x=fa[x][lg[deep[x]-deep[y]]-1];//往上面跳跃,deep[x]-deep[y]是高度差.-1是因为lg数组是Log值大1
	if(x==y)//发现Lca(x,y)=y
		return x;//返回吧,找到了...
	for(int k=lg[deep[x]]-1; k>=0; k--) //从大到小,枚举我们所需要的长度.2^(log(deep[x]))~1
		if(fa[x][k]!=fa[y][k])//如果发现x,y节点还没有上升到最近公共祖先节点
		{
    
    
			x=fa[x][k];//当然要跳跃
			y=fa[y][k];//当然要跳跃
		}
	return fa[x][0];//必须返回x的父亲节点,也就是Lca(x,y)
}
int main()
{
    
    
	scanf("%d%d%d",&n,&m,&s);//n个节点,m次询问,s为根节点
	for(int i=1; i<n; i++) //n-1条边
	{
    
    
		scanf("%d%d",&x,&y);//读入边
		add(x,y);//建立边
		add(y,x);//建立无向图
	}
	dfs(s,0);//从根节点,开始建立节点之间的跳跃关系,根节点的父亲节点没有,故选择0
	for(int i=1; i<=n; i++)
		lg[i]=lg[i-1]+(1<<lg[i-1]==i);//处理log数组的关系,lg[x]=log(x)+1,请记得最后使用要-1
	for(int i=1; i<=m; i++)
	{
    
    
		scanf("%d%d",&x,&y);//读入需要查询的节点
		printf("%d\n",LCA(x,y));//输出查询的结果
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Stydwn/article/details/105247956