首页 > 其他分享 >倍增 LCA

倍增 LCA

时间:2024-02-05 14:47:59浏览次数:36  
标签:结点 code int 路径 ans 倍增 LCA

【朴素 LCA】

LCA 是树的一个重要概念,意为两结点的最近公共祖先。

先给出朴素求 LCA 的代码。

int get_LCA(int u, int v) {
    if (d[u] > d[v])
    	swap(u, v);
    while (d[u] != d[v])
        v = p[v];
    while (u != v)
        u = p[u], v = p[v];
    return u;
}

其中 \(d_i\) 表示结点 \(i\) 的深度,\(p_i\) 表示结点 \(i\) 的父节点。

但是,这样最坏情况下需要遍历整棵树,时间复杂度为 \(O(n)\)。

我们需要优化这个过程。

观察可知,时间复杂度太高的瓶颈在于每次都只能往上提一个结点。

那有没有办法可以一次多提一些结点呢?有的。

它就是倍增算法。

【倍增 LCA】

定义 \(f_{i,\;j}\) 为结点 \(i\) 的 \(2^j\) 级祖先。(零级是父结点,一级往上提两个,二级往上提四个,三级提八个,以此类推)

这个数组预处理也很简单:

\(f_{i,j}=f_{f_{i, j-1},j-1}\),其实就是 \(2^j=2^{j-1}+2^{j-1}\)

看看有了这个数组要怎么求 LCA 。

假设现在要求 \(u\) 和 \(v\) 的 LCA 。

朴素 LCA 分两步:一是使 \(d_u=d_v\),二是一个一个提。

倍增 LCA 也是一样的步骤,不妨 \(v\) 比 \(u\) 深。

循环 \(i\) 从 \(\log_2^{\;\,n}\) 到 \(0\)。

每次如果 \(d_{f_{v,\;i}} \ge d_u\),就令 \(v = f_{v,\;i}\)。

这相当于把所差的深度二进制分解了,从高到低遍历每一位。

这时 \(d_u=d_v\),这里有一个需要注意的点:若 \(u=v\),返回 \(u\)。

这个特判就是判定 \(u\) 的子树是否包含 \(v\)。

然后我们继续循环 \(i\) 从 \(\log_2^{\;\,n}\) 到 \(0\)。

每次如果 \(f_{u,\;i}\neq f_{v,\;i}\),则使 \(u=f_{u,\;i}\),\(v=f_{v,\;i}\)。

可是 LCA 不是 \(u=v\) 吗?为什么是不等于呢?

因为我们没法判断在不在 LCA 上面,所以只好提升到 LCA 下面一个。

也因此我们最后返回的是 \(p_u\) 。

【code】

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
#define elif else if

int n, m, s, d[500005];
vector<int> e[500005];
int pw[25] = {1}, lg[500005] = {0};
int p[25][500005];//p[j][i]为i的2^j代祖先 
void srh(int x, int ftr, int dep)//当前点,父节点,深度 
{
	d[x] = dep, p[0][x] = ftr;
	for (int i = 1; i <= 20; i++)
		p[i][x] = p[i - 1][p[i - 1][x]];
	for (auto i : e[x])
		if (i != ftr)
			srh(i, x, dep + 1);
}

void init()
{
	for (int i = 1; i <= 20; i++)
		pw[i] = pw[i - 1] * 2;
	for (int i = 1; i <= n; i++)
		lg[i] = lg[i / 2] + 1;
//	for (int i = 1; i <= 20; i++)
//		for (int j = 1; j <= n; j++)
//			p[i][j] = p[i - 1][p[i - 1][j]];
}

int anc(int x, int k)//x的k层祖先 
{
	for (int i = 20; i >= 0; i--)
		if (k >= pw[i])
			x = p[i][x], k -= pw[i];
	return x;
}

int lca(int x, int y)
{
	if (d[x] < d[y])
		swap(x, y);
	x = anc(x, d[x] - d[y]);
	if (x == y)
		return x;
	for (int i = 20; i >= 0; i--)
		if (p[i][x] != p[i][y])
			x = p[i][x], y = p[i][y];
	return p[0][x];
}

int main()
{
	cin >> n >> m >> s;
	for (int i = 1, x, y; i < n; i++)
		cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
	init();
	srh(s, s, 0);
	for (int i = 1, a, b; i <= m; i++)
		cin >> a >> b, cout << lca(a, b) << endl;
	return 0;
}

【LCA 的扩展】

  1. 树上路径,可以视作两个点分别走到 LCA。两点距离可以用深度差求出来。

仓鼠找 sugar:两条路径有交点,等价于有一个 LCA 在另一条路上

Misha, Grisha and Underground

三个点取LCA,一定有两个点先汇合,不妨A、B先,lca(A,B)=X

那么三个点之间的路径相当于从X伸出去的三条中走两条。

X到A,X到B,X到C 的距离,求最大值即可。

A and B and Lecture Rooms

推论:如果 \(z\) 到 \(x,y\) 的距离相等,则 \(z\) 到 \(x,y\) 的路径一定是先到 \(x,y\) 的中点,再去 \(x,y\) 。

推论证明:

因为任意三点在树上的形态一定是这样:

所以 \(z\) 到 \(x,y\) 一定是先到了路径上,再分别去 \(x,y\)。(直接去 \(x\),再从 \(x\) 去 \(y\) 也可以)

而从 \(z\) 去 \(x,y\) 路径上的部分是一样的,要长度相等,到路径上一定是到了中点。

证毕。

再看。

设 \(m\) 是 \(x,y\) 中点,\(LCA=lca(x,y)\)

  1. \(m\neq LCA\)

只有图中的绿色部分可以先到 \(m\),再到 \(x,y\) 。

这些点的个数就是 \(sz[m]-sz[u]\),\(u\) 是 \(m\) 向 \(x\) 方向的子结点。

  1. \(m=LCA\)

除了红色部分都可以(两个子结点的子树)。

个数就是 \(n-sz[u]-sz[v]\),\(u,v\) 是两个子结点。

询问之前搜一遍,预处理 深度、倍增LCA等。

如果距离是奇数,没有点。

如果距离是偶数:

让深度较深的点往上走 \(dis/2\) 个,这个就是中点。

判断中点是不是 LCA,按上面分类讨论。

找对应的子结点用 \(anc()\) 函数即可。

紧急集合

结论:三个点的LCA,到三个点的距离之和最小。

证明:

只需证明到其他点不会更好。

设 \(T\) 是三点 LCA 。

  1. 在 \(T\) 到三点的路径上的其他点

如果把这个点往 \(T\) 方向挪一个,总体距离会减少 1.

  1. 不在路径上的其他点

设这个点是 \(u\),\(u\) 到 \(b,c\) 一定先到 \(b,c\) 路径上。

而从这个 \(b,c\) 路径上的点都比 \(T\) 远(上一类),\(u\) 一定比 \(T\) 更远。

综上,三点 LCA 到三个点的距离之和最小。

【题目分析】

火柴排队

这是一道贪心题。

\(\sum\,(a_i-b_i)^2=\sum\,({a_i}^2+{b_i}^2-2a_ib_i)\)

而 \({a_i}^2+{b_i}^2\) 是固定的,所以我们为了使值最小,那么 \(a_ib_i\) 就要最大。

根据排序不等式,顺序和 \(\geq\) 乱序和 \(\geq\) 逆序和。

所以我们只需要把两列火柴从小到大排序即可。

先记录下编号,把两列火柴排序之后,把一列火柴的编号当作下标,另一列火柴的编号当作值,两个同样位置的火柴就构成元素。

求这些元素构成的数组的逆序对数即可。

这题下标关系有点乱,debug了10min。

code


Max Flow P松鼠的新家:树上差分。

我们要给树上的一条路径上所有点加一个相同的权。

这条路径设为 \(u,v\),记 \(LCA=lca(u,v)\)

我们只需要使 \(ans_u\),\(ans_v\) 各加一,\(ans_{LCA}\),\(ans_{fa[LCA]}\) 各减一即可。

最后一个结点的权,就是它子树里所有 \(ans\) 的和。

这样,我们相当于在树上做了一个差分。

\(ans_u\!+\!+\),把根结点到 \(u\) 的都加了 1 .

\(ans_v\!+\!+\),把根结点到 \(v\) 的都加了 1 .

\(ans_{LCA}\!-\!-\),把根结点到 \(LCA\) 的都减了 1 .

\(ans_{fa[LCA]}\!-\!-\),把根结点到 \(fa[LCA]\) 的都减了 1 .

这样我们看一下,从 \(u,v\) 到 \(LCA\) 的只被加了一个 1,没被减过。

\(LCA\) 加了两个 1,但是减了一个 1,抵消了。

从 \(fa[LCA]\) 到根结点,加了两个 1,也减了两个 1,抵消。

操作之后,再搜索一遍求子树和即可。

(注:本来还有两个 \(ans_0\!-\!-\),但是因为 0 不在范围内就不写)

松鼠的新家还有一个坑点,不能直接把访问顺序连续两个看作路径

因为这样的话所有 既做过终点也做过起点的(2~n-1)就会多算。

所以最后还得循环减掉这多余的一个。

code of Max Flow P

code of 松鼠的新家


灾难

首先为了方便,给所有入度为 0 的点都添加一个父亲(太阳)。

接着,我们可以发现一件事情:

如果生物 \(a\) 会灭绝,等价于 \(a\) 所有的食物都会灭绝。

而 \(a\) 所有的食物都会灭绝,等价于这些食物的 \(LCA\) 会灭绝。

被捕食者的 vector 存储捕食者(食物链同向)。

把太阳设置为 0 。

拓扑排序,每删除一个点,就把所有捕食它的生物的入度减一。

还要更新所有捕食它的生物的食物 LCA。

如果入度为 0,加入队列,并把这个结点连向其食物LCA,食物 LCA 为父结点。,并更新这个结点的各层倍增LCA。

拓扑排序结束后,开始建二号图。

根据上面加粗的字体连的边,建成一棵树,0 号结点为根。

每个结点的 “灾难值” 就是这个点的子树规模 - 1.(不算自己)

code


堆积木KLO

\(dp[i]\) 表示当第 \(i\) 块积木归位时,前 \(i\) 块积木的最大归位数。

考虑 \(dp[i]\) 能由 \(dp[j]\) 转移的条件:

在 \(a[i]\) 能归位的情况下,\(a[j]\) 也能归位。

首先,\(a[i]\) 能归位,需要 \(a[i]\leq i\) 。

其次,\(a[j]\) 能归位,需要 \(a[j]\leq j\),但因为我们把不可行赋值成 \(-\infty\),所以不用理会。

再次,\(a[i] > a[j]\) 。

然后,\(a[j]\) 需要往下掉 \(j-a[j]\) 格,\(a[i]\) 也随之掉 \(j-a[j]\) 格,所以现在 \(a[i]\) 在 \(i-(j-a[j])=i-j+a[j]\) 格。

而 \(a[i]\) 要到 \(a[i]\) 处,所以 \(i-j+a[j]\ge a[i]\),即 \(i-a[i]\geq j-a[j]\)。

所以:

\(i>j,a[i]-a[j]\leq i-j, i-a[i]\leq a[j]-j\)

而后两个条件可以推出第一个条件。

\(a[i]-a[j]\leq i-j, i-a[i]\leq a[j] - j\)

在 DP 之前把 \(a\) 按照 \(i - a[i]\) 排个序,可以再消除一个条件。

当然这就需要额外记录原来的编号,所以建议结构体。

\(dp[i]=max(dp[j]\;\,|\;\,a[i]>a[j])+1\)

所以来一个树状数组维护最大值,每次更新完就把 \(tr[i]\) 加上 \(dp[i]\) 。

更新时查询一下小于 \(a[i]\) 的最大值再加一。

code


运输计划

题意简述:

给定一棵 \(n\) 个结点的带边权树和 \(m\) 条树上路径。

现在可以把一条边的边权改为 0 。

问:这 \(m\) 条路径上边权总和最大值最小是多少?

最大值最小,明摆着告诉你要二分。

那我们就二分,显然越小越好,越大越可行。

\(chk(mid)=true\) 表示最大值等于 \(mid\) 时可行。

现在我们的任务就变成了判断最大值是 \(mid\) 时可不可行。

最大值是 \(mid\) 可行,等价于:存在一条边删去之后,所有的路径的边权和都小于等于 \(mid\) 。

又等价于:存在一条边删去后,最长的边边权小于等于 \(mid\),而且这条边是所有边权和大于 \(mid\) 的路径的公共边。

而一条边是公共边,就相当于这条边重复的次数就是路径条数。

code

这个东西可以用树上差分。

标签:结点,code,int,路径,ans,倍增,LCA
From: https://www.cnblogs.com/FLY-lai/p/18007940

相关文章

  • 浅谈LocalCache | 京东云技术团队
    1、什么是LocalCache?本地缓存是一种将数据存储在应用程序内存中的机制,用于提高数据访问的性能和响应速度。它通过在内存中维护一个键值对的存储结构,允许应用程序快速检索和访问数据,而无需每次都从慢速的数据源(如数据库或网络)获取数据。2、LocalCache优缺点1)优点•快速访问:Loca......
  • 浅谈LocalCache | 京东云技术团队
    1、什么是LocalCache?本地缓存是一种将数据存储在应用程序内存中的机制,用于提高数据访问的性能和响应速度。它通过在内存中维护一个键值对的存储结构,允许应用程序快速检索和访问数据,而无需每次都从慢速的数据源(如数据库或网络)获取数据。2、LocalCache优缺点1)优点•快速访问:LocalCach......
  • 【算法】LCA
    什么是LCA?LCA(LeastCommonAncestors),即最近公共祖先,是指在有根树中,找出某两个结点x和y最近的公共祖先。三种算法有三种算法可以求解LCA问题,分别为朴素算法、倍增算法和Tarjan算法。朴素算法倍增算法和Tarjan算法都在建立在朴素算法的思想下,因此,了解朴素算法的思想有助于更......
  • 树上倍增
    第2题    [模板]LCA查看测评数据信息模板题:求LCA(最近公共祖先)。以1号节点为树的根。输入格式第一行两个正整数n,m,表示树的节点数和询问的个数。接下来n-1行,每行两个正整数u,v,表示树上有一条连接节点u和v的边。接下来m行,每行两个正整数u,v,表示询问节点u......
  • 基于volcano实现节点真实负载感知调度
    本文分享自华为云社区《基于volcano实现节点真实负载感知调度》,作者:可以交个朋友。背景默认调度器调度器视某个节点的空闲可调度资源=节点可分配资源-SUM(节点上已调度Pod们的request),当某个Pod处于pending状态待调度时,默认调度器根据Pod中指定的request值和各个节点的空闲......
  • LCA
    LCA定义\(树上两点向上跳到最近の重点\)1/\23/\\456//LCA(4,5)=2;//LCA(2,6)=1;如何求倍增思想设$x_1、x_2、x_3····x_k\inZ$总会有\(\alpha\)=$2^{x_1}+···+......
  • CF-1184-E3-最小生成树+倍增+并查集
    1184-E3题目大意给定一个\(n\)个点,\(m\)条边的无向图,边带权。对于每条边,你需要找到最大值\(x\),使得把这条边的权值修改为\(x\)后能够出现在最小生成树上。Solution先把整个图的最小生成树弄出来,然后将边分为树边以及非树边来考虑:非树边:对于一个非树边连接了\(x\)和\(y\)的......
  • CF-702-E-倍增
    702-E题目大意\(n\)个点,每个点有一条出边,边带权。给定整数\(k\)。求从每个节点出发经过\(k\)条边的路径上所有的边权和,以及最小的边权。Solution给定的图是基环树森林,从任意一个点出发无限走下去一定会进入环内。倍增板子题,这里不详细解释什么是倍增数组,具体的可以网上自行......
  • DFS求LCA
    Updateon2023.7.17:该技巧目前已知的最早来源:skip2004。Updateon2023.7.17:比较时,取时间戳较小的结点也是正确的,不用记录深度。DFS序求LCA无论是从时间常数,空间常数还是好写程度方面均吊打欧拉序。定义DFS序表示对一棵树进行深度优先搜索得到的结点序列,而时间戳DF......
  • Hazelcast 的事务处理与一致性保证
    1.背景介绍在现代分布式系统中,事务处理和一致性保证是非常重要的问题。Hazelcast是一个高性能的分布式计算平台,它提供了一种高效的事务处理和一致性保证机制。在这篇文章中,我们将深入探讨Hazelcast的事务处理和一致性保证机制,并分析其核心概念、算法原理、实现细节以及未来发展......