LCA

概念:

两个点最近的共有的祖先节点。

求法:

1.向上标记法

这种算法,emmmmm....可以把它理解为是暴力,即:

假设树上有两个点x,y,那么从x走到根节点,将路过的点都进行标记,再从y向上走到根节点,第一次遇到已经标记过的节点时,就找到了LCA(x,y)。

2.Tarjan求LCA

该算法是一种离线算法,即在一次操作中将问题全部解决。具体过程如下:

1.枚举与当前节点有边相连的所有点,如果该点未被访问过,则从该点开始,返回1。(即dfs遍历每一棵子树)

2.将该节点设为自己的父亲节点,若该点为叶子节点或子节点已全部枚举完毕,则寻找与它有关系的节点,若有与它有关系的点且那个点已经被标记过,则当前它们的父亲节点的编号就是它们的最近公共祖先。

3.将该节点与自己的父亲节点合并。

总复杂度为:O(n+q)

代码如下:

int d[N];
bool vis[N];
void Tarjan(int k) {
	vis[k]=1;
	for(int i=head[k];i;i=next[i]) {
		if(vis[ver[i]]) continue; 
		Tarjan(ver[i]);
		fa[ver[i]]=k;
	}
	for(int i=q_head[k];i;i=q_next[i]) 
		if(vis[q_ver[i]]) 
			ans[q_num[i]]=getfather(q_ver[i]);
} 

例题:

HDU2586 How far away?

题目大意:

有一条n-1条边的树。有m个询问,分别求两点的距离。

分析:

用一个dist数组来表示到根节点的距离,则两点的距离即为dist[i]+dist[j]-2*dist[lca].

代码如下:

#include
#include
//杭电不支持万能头...
using namespace std;
#define N 200001
#define M 200001
int n,m,head[N],q_head[M],ver[N],q_ver[M],nxt[N],q_next[M],tot,q_num[N],q_tot,aska[N],askb[N],edge[N],T;
//杭电next为敏感词...
int fa[N],ans[M],dist[N];
bool vis[N];

inline void add(int x,int y,int z) {
	nxt[++tot]=head[x];
	head[x]=tot;
	ver[tot]=y;
	edge[tot]=z;
}

inline void add_(int x,int y,int number) {
	q_next[++q_tot]=q_head[x];
	q_num[q_tot]=number;
	q_head[x]=q_tot;
	q_ver[q_tot]=y;
}

inline void init() {
	memset(q_head,0,sizeof(q_head));
	memset(vis,0,sizeof(vis));
	memset(head,0,sizeof(head));
	memset(ans,0,sizeof(ans));
	memset(dist,0,sizeof(dist));
	memset(aska,0,sizeof(aska));
	memset(askb,0,sizeof(askb));
	tot=0,q_tot=0;
    //初始化
	scanf("%d%d",&n,&m);
	for(int i=1;i

 

你可能感兴趣的:(LCA)