LCA的学习(DFS+LCA的简单板子)

最近碰到的一些题,主要问的就是LCA(最小公共祖先)

一开始是暴力,但是随着数据的增多,预感到不能继续暴力了,于是开始学习关于 最小公共祖先 的算法

网上有很多关于这方面的博客,但是很多的文章让我感觉看的云里雾里的(本人太菜),终于在浩如烟海的众多文章中找到了相对来说比较简单的一些,东拼西凑,最后总算稍微了解一点LCA算法了;

下面就介绍一下简单的LCA实现(个人觉得非常好理解)(目前我认为的LCA)

LCA算法就是在一颗树上找两个叶子节点的公共祖先,算法的前提要保证只有一棵树(家族的话只有一个家族),而且必须知道根节点,然后来确定这棵树(这棵树不能被改变了)

然后对于节点的查询,仍是让两个节点向上寻找,直到两个节点相同,即为要寻找的 最小公共祖先 ;

LAC的倍增;
引入一个数组,fa[i][j];对于节点i,它的第2^j个父亲节点的编号 
第i个节点的第2^j个父亲节点的编号;
建图:
利用临界表,从根节点出发,用DFS来建表。
这里每次son的建边都需要对它的fa[son][]数组进行更新,
fa[son][i+1] = fa[ fa[son][i] ][i];(倍增思想) 
保证deep[X]>=deep[Y];只操作X(或只操作Y),
让x向上搜,知道其深度等于y的深度,
这里通过倍增算法使其快速上搜 ,等二者深度相同时,同时上溯至同一节点;
此节点即为x与y的LCA;

其中用到了倍增的思想,废话不多说,树应该很容易就能想到,因此直接就上代码了:

首先对数据的初始化:

int fa[MAXN][30],deep[MAXN];
//fa[i][j]中记录的是i节点的第2^j的祖先是谁(fa中存的就是祖先)
//deep[i]记录i节点的深度
int k;
int n,m;
 
void intt()
{
	clean(head,-1);//向前星的初始化
	clean(deep,0);//深度为0
	clean(fa,-1);//初始化父节点都是-1
	ecnt=0;
	k=1;
}

先建边(我用的是向前星模拟连接表,核心没有变,因此用自己熟悉的随便什么都行,无所谓)

struct node{
	int v,w,nxt;
	node(int _v=0,int _nxt=0):
	v(_v),nxt(_nxt){}
}edge[MAXN<<1];
int head[MAXN],ecnt;
//事先初始化
void add(int u,int v)
{
	edge[ecnt]=node(v,head[u]);
	head[u]=ecnt++;
}

然后是建树;

建树的时候用deep数组记录一下该节点到根节点的距离(该节点的深度)

注意,因为节点可能会重复访问,因此要有一个bool vis[]数组标记一下,防止被重复访问,我这里deep数组顺被充当vis,因此就没有写

void dfs(int u)
{
	for(int i=head[u];i+1;i=edge[i].nxt)//便利这个边的邻接表
	{
		int temp=edge[i].v;
		if(deep[temp]==0)//如果这个节点没有被标记
		{
			deep[temp]=deep[u]+1;//深度+1
			fa[temp][0]=u;//父节点是u 
			int up=0,pre=u;//从父节点开始向上找 
			while(fa[pre][up]>=0)//
			{
				fa[temp][up+1]=fa[pre][up];//子节点的第2^(up+1)父辈是pre的2^up父辈 
				pre=fa[pre][up++];//刷新pre
			}
			dfs(temp);//该节点继续向下走
		}
	}
}

然后是LCA查找(倍增)了:

int lca(int a,int b)
{
	if(deep[a]<deep[b])
		swap(a,b);//默认a节点的深度最深
	int lim=log2(deep[a])+1;//最多访问的第几个夫节点
	//使他们的深度相同 
	for(int i=lim;i>=0;--i)
	{
		if(deep[fa[a][i]]>=deep[b])
			a=fa[a][i];
	}
    //现在a和b的深度相同
	if(a==b)
		return a;
    //寻找最小的公共祖先
	for(int i=lim;i>=0;--i)
	{
        //如果fa[a][i]==fa[b][i],说明他们的公共祖先深度可能更小,因此不用更新,向更小的方向查找
        //如果fa[a][i]!=fa[b][i],说明目前他们的公共祖先比当前的祖先深度更浅,但是要深于上一个i,因此刷新a和b,向更小的范围查找祖先
		if(fa[a][i]!=fa[b][i])
		{
			a=fa[a][i];
			b=fa[b][i];
		}
	}//最后发现祖先
	if(fa[a][0]==fa[b][0])
		return fa[a][0];
	else
		return -1;
}

这就是LCA的步骤了

下面是完整的代码,例题:hihoCoder #1069 : 最近公共祖先·三

#include<stdio.h>
#include<string.h>  
#include<math.h>  
  
#include<map>   
//#include<set>
#include<deque>  
#include<queue>  
#include<stack>  
#include<bitset> 
#include<string>  
#include<fstream>
#include<iostream>  
#include<algorithm>  
using namespace std;  
 
#define ll long long  
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b) 
#define clean(a,b) memset(a,b,sizeof(a))// 水印 
//std::ios::sync_with_stdio(false);
const int MAXN=1e5+10;
const int INF=0x3f3f3f3f;
const ll mod=1e9+7;
 
struct node{
	int v,w,nxt;
	node(int _v=0,int _nxt=0):
	v(_v),nxt(_nxt){}
}edge[MAXN<<1];
int head[MAXN],ecnt;
int fa[MAXN][30],deep[MAXN];
int k;
int n,m;
 
map<string,int> mp1;
map<int,string> mp2;
 
void intt()
{
	mp1.clear();
	mp2.clear();
	clean(head,-1);
	clean(deep,0);
	clean(fa,-1);
	ecnt=0;
	k=1;
}
 
void add(int u,int v)
{
	edge[ecnt]=node(v,head[u]);
	head[u]=ecnt++;
}
 
void dfs(int u)
{
	for(int i=head[u];i+1;i=edge[i].nxt)
	{
		int temp=edge[i].v;
		if(deep[temp]==0)
		{
			deep[temp]=deep[u]+1;
			fa[temp][0]=u;//父节点是u 
			int up=0,pre=u;//从父节点开始向上找 
			while(fa[pre][up]>=0)
			{
				fa[temp][up+1]=fa[pre][up];//子节点的第2^(up+1)父辈是pre的2^up父辈 
				pre=fa[pre][up++];
			}
			dfs(temp);
		}
	}
}
 
int lca(int a,int b)
{
	if(deep[a]<deep[b])
		swap(a,b);
	int lim=log2(deep[a])+1;
	//使他们的深度相同 
	for(int i=lim;i>=0;--i)
	{
		if(deep[fa[a][i]]>=deep[b])
			a=fa[a][i];
	}
	if(a==b)
		return a;
	for(int i=lim;i>=0;--i)
	{
		if(fa[a][i]!=fa[b][i])
		{
			a=fa[a][i];
			b=fa[b][i];
		}
	}
	if(fa[a][0]==fa[b][0])
		return fa[a][0];
	else
		return -1;
}
 
int main()
{
	std::ios::sync_with_stdio(false);
	intt();
	cin>>n;
	string name1,name2;
	for(int i=1;i<=n;++i)
	{
		cin>>name1>>name2;
		if(mp1[name1]==0)//name1对应int 
		{
			mp1[name1]=k;
			mp2[k++]=name1;
		}
		if(mp1[name2]==0)//name2对应int 
		{
			mp1[name2]=k;
			mp2[k++]=name2;
		}
		add(mp1[name1],mp1[name2]);
//		add(mp1[name2],mp1[name1]);
	}
	deep[1]=1;
	dfs(1);
	cin>>m;
	for(int i=1;i<=m;++i)
	{
		cin>>name1>>name2;
		if(name1==name2)
			cout<<name1<<endl;
		else
		{
			int ans=lca(mp1[name1],mp1[name2]);
			if(ans==-1)
				cout<<ans<<endl;
			else
				cout<<mp2[ans]<<endl;
		}
	}
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值