LCA

最近公共祖先_LCA

给定一棵有根树,若节点z即可以到x也可以到y,则称z为xy的公共祖先,公共祖先中深度最大(即距离x,y最近)的则是xy最近公共祖先,称为LCA(x,y)。

以洛谷的P3379【模板】最近公共祖先(LCA)进行讲解:
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式
第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的最近公共祖先。

输出格式
输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例
输入 #1

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

输出 #1

4
4
1
4
4

说明/提示
对于 30%的数据,N\leq 10N≤10,M\leq 10M≤10。

对于 70%的数据,N\leq 10000N≤10000,M\leq 10000M≤10000。

对于 100% 的数据,N\leq 500000N≤500000,M\leq 500000M≤500000。

样例说明:

该树结构如下:

第一次询问:2, 42,4 的最近公共祖先,故为 44。

第二次询问:3, 23,2 的最近公共祖先,故为 44。

第三次询问:3, 53,5 的最近公共祖先,故为 11。

第四次询问:1, 21,2 的最近公共祖先,故为 44。

第五次询问:4, 54,5 的最近公共祖先,故为 44。

故输出依次为 4, 4, 1, 4, 44,4,1,4,4。

向上标记法\(O(mn)\)

在第一次dfs时,记录每个节点的pre。

对于每个询问的x,y,先通过pre将两者调成到深度相同的状态,再向上调整,指导x'==y'。

时间复杂度为\(O(mn)\)。本题会TLE3个点。

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+5;
int n,m,root;
int te,tail[N],pre[N],dep[N];
struct e_
{
	int v,pre;
}e[N*2];

inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}

void dfs(int u)
{
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		
		if(dep[v]) continue;
		
		pre[v]=u;
		dep[v]=dep[u]+1;
		
		dfs(v);
	}
}
int ask(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	while(dep[x]>dep[y]) x=pre[x];
	while(x!=y) x=pre[x],y=pre[y];
	return x;
}

int main()
{
	scanf("%d %d %d",&n,&m,&root);
	
	for(int i=1,u,v;i<n;++i)
	{
		scanf("%d %d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dep[root]=1;
	dfs(root);
	
	for(int i=1,x,y;i<=m;++i)
	{
		scanf("%d %d",&x,&y);
		printf("%d\n",ask(x,y));
	}
}

树上倍增法\(O(mlogn)\)

对于每个节点,\(F[x,k]\)储存x第2k个祖先。

时间复杂度为\(O(mlogn)\)。本题可AC。

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+5;
int n,m,root;
int te,tail[N],f[N][25],dep[N];
struct e_
{
	int v,pre;
}e[N*2];

inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}

void dfs(int u)
{
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		
		if(dep[v]) continue;

		dep[v]=dep[u]+1;
		 
		f[v][0]=u;    
		for(int j=1;(1<<j)<dep[v];++j) f[v][j]=f[f[v][j-1]][j-1];
		
		dfs(v);
	}
}
int ask(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	
	for(int i=20;i>=0;--i)
	if(dep[v]+(1<<i)<=dep[u]) u=f[u][i];
	
	if(u==v) return u;
	
	for(int i=20;i>=0;--i)
	if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
	
	return f[u][0];
}

int main()
{
	scanf("%d %d %d",&n,&m,&root);
	
	for(int i=1,u,v;i<n;++i)
	{
		scanf("%d %d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dep[root]=1;
	dfs(root);
	
	for(int i=1,x,y;i<=m;++i)
	{
		scanf("%d %d",&x,&y);
		printf("%d\n",ask(x,y));
	}
}

Tarjan\(O(n)\)

离线储存所有需要查询的(x,y)。

对于DFS到每个节点时,有三种状态,一种为已经回溯了,一种是递推但未回溯的,一种是尚未递推的。

对于第一种状态的节点,我们采用并查集将它并到第二种状态的点里,即f[y]=相连的第一个尚未回溯的点,则对于x和这部分点的最近公共祖先为f[y]。

对于第二种状态的节点,在递归回溯后也可转移为第一种状态的情况。

对于第三种状态的节点,在递归到第三种状态的点时,现在的节点x则为一二状态,仍可转移到第一种状态的情况。

时间复杂度为O(n)。本题可AC。

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+5;
int n,m,root;
int te,tt,tail[N],tail2[N],f[N],zt[N];
struct e_
{
	int v,pre;
}e[N*2];
struct t_
{
	int u,v,val,preu,prev;
}t[N];
inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}
inline void add_(int u,int v)
{
	t[++tt]=(t_){u,v,0,tail2[u],tail2[v]};
	tail2[u]=tail2[v]=tt;
}

int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}

void dfs(int u,int fa)
{
	f[u]=u;
	zt[u]=1;
	
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
	}
	
	for(int i=tail2[u];i;i=t[i].u==u?t[i].preu:t[i].prev)
	{
		int v=t[i].u==u?t[i].v:t[i].u;
		if(!t[i].val&&zt[v]==2) t[i].val=find(v);
	}
	f[u]=fa;
	zt[u]=2;
}
int main()
{
	scanf("%d %d %d",&n,&m,&root);
	
	for(int i=1,u,v;i<n;++i)
	{
		scanf("%d %d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(int i=1,u,v;i<=m;++i)
	{
		scanf("%d %d",&u,&v);
		add_(u,v);
	}
	
	dfs(root,-1);
	for(int i=1;i<=m;++i)
	printf("%d\n",t[i].val);
}
posted @ 2020-10-23 20:01  林生。  阅读(156)  评论(0)    收藏  举报