C/C++中的LCA(Least Common Ancestors) 最近公共祖先

今天复习了LCA,主要是倍增LCA和树链剖分LCA,还有tarian LCA还没学,有需要的话再补

倍增LCA

求LCA需要先用dfs求出每一个点的深度dep[i]。
在基本LCA(x,y)中x为更深的点,(不是则交换x和y)x不断向上跳直到x=y

在倍增LCA中fa[i][j]表示i号节点向上走2^j所到的点当dep-2^j>=1时有效

同样使X深度更深,从小到大枚举当dep[ fa[x][j] ]不超过dep[y]时x才能往上跳

跳完后必然有dep[x]=dep[y],若x==y则返回否则同时上跳保持x!=y,最后一段停留在LCA(x,y)下方
返回fa[x][0],代码如下

#define MAXN 200005
#define LOG 20

int n, q;
vector<int> v[MAXN];
int fa[MAXN][LOG], dep[MAXN];
map<pair<int, int>, int> lca_cache; // 区间 LCA 缓存

// 深度优先搜索,初始化父节点和深度
void dfs(int x, int p) {
    dep[x] = dep[p] + 1;
    fa[x][0] = p;
    for (const auto& i : v[x]) {
        if (i == p) continue;
        dfs(i, x);
    }
}

// 倍增表预处理
void preprocess() {
    for (int i = 1; i < LOG; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (fa[j][i - 1] != 0) {
                fa[j][i] = fa[fa[j][i - 1]][i - 1];
            }
        }
    }
}

// 求最近公共祖先
int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = LOG - 1; i >= 0; --i) {
        if (dep[x] - (1 << i) >= dep[y]) {
            x = fa[x][i];
        }
    }
    if (x == y) return x;
    for (int i = LOG - 1; i >= 0; --i) {
        if (fa[x][i] != fa[y][i]) {
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}
int main()
{
...
    // 深度优先搜索初始化
    dfs(1, 0);

    // 倍增表预处理
    preprocess();
...
}

树链剖分LCA

树链剖分LCA需要将节点按树链剖分,再按树链的特性使用LCA,树链就是所有节点之间组成的树形图,按重链剖分就是找出数组中最长的链、并标记每个节点属于哪一个树链。(重链就是当前节点最长的链,每个节点有且仅属于一条链)。

再分情况讨论:

如果树链链头相同,说明在同一个树链里,则位置高的即为公共祖先。

如果链头不同,则链头深度较小的节点进入链头的父节点,再进行比较。

整理去年的笔记里面还有出入序但是似乎没有用上可以忽略不看(那是一会会用上的神奇妙妙工具,等我复习到怎么用了先)

vector<int> v[maxn];//每个节点的关系容器,同上
int ord;//dfs序计数,出入序用的
int rnk[maxn];//rnk[i]表示入序为i的节点编号
struct NODE{
	int fa;//当前节点的父节点
	int in;//当前节点的入序
	int out;//当前节点的出序
	int son;//当前节点重儿子的编号
	int top;//当前节点所属链的首节点
	int deep;//当前节点所在树中的深度
	int size;//当前节点子树大小
}node[maxn];
void dfs1(int t,int fa,int deep)//主要统计size,找重链
{
	node[t].size=1;
	node[t].fa=fa;
	node[t].deep=deep+1;
	for(auto i:v[t])
	{
		if(i!=fa)
		{
			dfs1(i,t,deep+1);
			node[t].size += node[i].size;
			if(node[i].size>node[node[t].son].size)
			{
				node[t].son = i;
			}
		}
		
	}
}
void dfs2(int t,int fa,int top)//分树链
{
	node[t].top=top;
	node[t].in=++ord;
	rnk[ord]=t;
	if(node[t].son){//如果是重链就连续,不是重链就分段
		dfs2(node[t].son,t,top);
	}
	for(auto i:v[t])
	{
		if(i!=node[t].fa&&i!=node[t].son)
		{
			dfs2(i,t,i);
		}
	}
	node[t].out = ord;
}
int lca(int x,int y)
{
	while(node[x].top!=node[y].top){
		if(node[node[x].top].deep < node[node[y].top].deep)
		{
			swap(x,y);
		}
		x=node[node[x].top].fa;
	}
	return node[x].deep >node[y].deep ? y : x;
}
signed main()
{
...
    //收集好节点的数据之后需要先进行树链剖分(两个dfs后才能使用LCA)
    dfs1(1,0,0);// 从根节点1开始,统计size和son
    dfs2(1,0,1);// 从根节点1开始,划分树链
...
}

对于我来说树链剖分的做法更容易理解一些但是代码更长而且运行效率不如倍增的运行效率,请根据不同的情况进行选择。

如有错漏欢迎指出,欢迎交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值