码迷,mamicode.com
首页 > 其他好文 > 详细

[洛谷P3379]【模板】最近公共祖先(LCA)

时间:2017-12-16 14:43:35      阅读:174      评论:0      收藏:0      [点我收藏+]

标签:gpo   .com   const   nbsp   方法   get   can   define   turn   

题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

题解:有多种做法,我这边用了倍增和树链剖分(都是在线而且好像都不是很快。。。)

 

倍增:(写于2017-9-13)

bfs函数:处理每个节点的深度(其实dfs更常用,当时我用的是bfs)和父亲(dad[i][0])

init函数:处理每个节点的二的整数幂的父亲,dad[i][j]存的是第i个节点的第$2^{j}$个祖先,dad[i][0]就是它的父亲,没有就是0。

可发现每个节点的第$2^{j}$个祖先是它的第$2^{j-1}$个祖先的$2^{j-1}$个祖先,也就是dad[i][j]=dad[dad[i][j-1]][j-1]($2^{n}$=$2^{n-1}$+$2^{n-1}$)。要特别注意一定要把关于j的循环放在外面,因为是一层一层处理的

LCA函数:求LCA,具体见程序中

 

C++ Code:

#include<cstdio>
#define maxn 500100
struct Edge{
	int next,to;
}edge[maxn<<1];
int dep[maxn],dad[maxn][20];
int head[maxn],cnt;
int q[maxn],v[maxn],t=0,w;
int n,m,root;
void swap(int &a,int &b){a^=b;b^=a;a^=b;}
void add(int a,int b){
	edge[++cnt].to=b;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
void bfs(){
	v[q[w=1]=root]=dep[root]=1;
	while (t<w){
		int x=q[++t];
		for (int i=head[x];i;i=edge[i].next){
			int ne=edge[i].to;
			if (!v[ne]){
				v[q[++w]=ne]=1;
				dep[ne]=dep[dad[ne][0]=x]+1;	
			}
		}
	}
}
void init(){
	for (int j=1;j<20;j++){
		for (int i=1;i<=n;i++){
			dad[i][j]=dad[dad[i][j-1]][j-1];
		}
	}
}
int LCA(int x,int y){
	if (dep[x]<dep[y])swap(x,y);//保证x的深度大于y 
	int temp6=dep[x]-dep[y];//让x的深度与y相同,可以使用二进制的思想,不过一般一个循环搞定(见下方注释处)
	for (int i=0;i<20;i++){
		if (temp6 & (1<<i))x=dad[x][i];
	}
	/*更常用的方法: 
	for (int i=19;i>=0;i--)if (dep[dad[x][i]]>=dep[y])x=dad[x][i];
	*/ 
	if (x==y)return x;//如果两个已经相同就返回其中一个 
	for (int i=19;i>=0;i--){//只要dad[x][i]!=dad[y][i],两个节点一起向上跳(如果相同表示跳多了) 
		if (dad[x][i]!=dad[y][i]){
			x=dad[x][i];
			y=dad[y][i];
		}
	}
	return dad[x][0];//因为不相同,所以再向上跳一个 
}
int main(){
	scanf("%d%d%d",&n,&m,&root);
	for (int i=1;i<n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	bfs();
	init();
	for (int i=0;i<m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",LCA(a,b));
	}
	return 0;
}

 

 树链剖分:(写于2017-12-15)

dfs1,dfs2函数:树链剖分,见树链剖分

LCA:求LCA,这一部分很简单,如果两个节点不在同一条重链上,就把其中重链顶部深度深的那个节点向上跳到其所在重链顶部的父亲。直到两个节点在同一条重链上,就返回深度较浅的一个

 

C++ Code:

#include<cstdio>
using namespace std;
const int maxn=500100;
int n,m,r,idx;
int dep[maxn],fa[maxn],top[maxn],dfn[maxn],siz[maxn],son[maxn];
int head[maxn],cnt;
struct Edge{
	int to,nxt;
}e[maxn<<1];
void swap(int &a,int &b){a^=b^=a^=b;}
void addE(int a,int b){
	e[++cnt]=(Edge){b,head[a]};
	head[a]=cnt;
}
void dfs1(int rt){
	siz[rt]=1;
	for (int i=head[rt];i;i=e[i].nxt){
		int ne=e[i].to;
		if (ne!=fa[rt]){
			fa[ne]=rt;
			dep[ne]=dep[rt]+1;
			dfs1(ne);
			if (son[rt]==0||siz[ne]>siz[son[rt]])son[rt]=ne;
			siz[rt]+=siz[ne];
		}
	}
}
void dfs2(int rt){
	dfn[rt]=++idx;
	int ne=son[rt];
	if (ne)top[ne]=top[rt],dfs2(ne);
	for (int i=head[rt];i;i=e[i].nxt){
		ne=e[i].to;
		if (ne==son[rt]||ne==fa[rt])continue;
		top[ne]=ne;
		dfs2(ne);
	}
}
int LCA(int x,int y){
	while (top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
int main(){
	scanf("%d%d%d",&n,&m,&r);
	for (int i=1;i<n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		addE(a,b),addE(b,a);
	}
	dep[top[r]=r]=1;
	dfs1(r);
	dfs2(r);
	while (m--){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",LCA(a,b));
	}
	return 0;
}

 

[洛谷P3379]【模板】最近公共祖先(LCA)

标签:gpo   .com   const   nbsp   方法   get   can   define   turn   

原文地址:http://www.cnblogs.com/Memory-of-winter/p/8046320.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!