倍增 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;
}
其中
但是,这样最坏情况下需要遍历整棵树,时间复杂度为
我们需要优化这个过程。
观察可知,时间复杂度太高的瓶颈在于每次都只能往上提一个结点。
那有没有办法可以一次多提一些结点呢?有的。
它就是倍增算法。
【倍增 LCA】
定义
这个数组预处理也很简单:
看看有了这个数组要怎么求 LCA 。
假设现在要求
朴素 LCA 分两步:一是使
倍增 LCA 也是一样的步骤,不妨
循环
每次如果
这相当于把所差的深度二进制分解了,从高到低遍历每一位。
这时
这个特判就是判定
然后我们继续循环
每次如果
可是 LCA 不是
因为我们没法判断在不在 LCA 上面,所以只好提升到 LCA 下面一个。
也因此我们最后返回的是
【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 的扩展】
- 树上路径,可以视作两个点分别走到 LCA。两点距离可以用深度差求出来。
仓鼠找 sugar:两条路径有交点,等价于有一个 LCA 在另一条路上
Misha, Grisha and Underground:
三个点取LCA,一定有两个点先汇合,不妨A、B先,lca(A,B)=X
那么三个点之间的路径相当于从X伸出去的三条中走两条。
X到A,X到B,X到C 的距离,求最大值即可。
推论:如果
推论证明:
因为任意三点在树上的形态一定是这样:
所以
而从
证毕。
再看。
设
只有图中的绿色部分可以先到
这些点的个数就是
除了红色部分都可以(两个子结点的子树)。
个数就是
询问之前搜一遍,预处理 深度、倍增LCA等。
如果距离是奇数,没有点。
如果距离是偶数:
让深度较深的点往上走
判断中点是不是 LCA,按上面分类讨论。
找对应的子结点用
紧急集合:
结论:三个点的LCA,到三个点的距离之和最小。
证明:
只需证明到其他点不会更好。
设
- 在
到三点的路径上的其他点
如果把这个点往
- 不在路径上的其他点
设这个点是
而从这个
综上,三点 LCA 到三个点的距离之和最小。
【题目分析】
火柴排队:
这是一道贪心题。
而
根据排序不等式,顺序和
所以我们只需要把两列火柴从小到大排序即可。
先记录下编号,把两列火柴排序之后,把一列火柴的编号当作下标,另一列火柴的编号当作值,两个同样位置的火柴就构成元素。
求这些元素构成的数组的逆序对数即可。
这题下标关系有点乱,debug了10min。
Max Flow P,松鼠的新家:树上差分。
我们要给树上的一条路径上所有点加一个相同的权。
这条路径设为
我们只需要使
最后一个结点的权,就是它子树里所有
这样,我们相当于在树上做了一个差分。
这样我们看一下,从
从
操作之后,再搜索一遍求子树和即可。
(注:本来还有两个
松鼠的新家还有一个坑点,不能直接把访问顺序连续两个看作路径
因为这样的话所有 既做过终点也做过起点的(2~n-1)就会多算。
所以最后还得循环减掉这多余的一个。
首先为了方便,给所有入度为 0 的点都添加一个父亲(太阳)。
接着,我们可以发现一件事情:
如果生物
而
被捕食者的 vector 存储捕食者(食物链同向)。
把太阳设置为 0 。
拓扑排序,每删除一个点,就把所有捕食它的生物的入度减一。
还要更新所有捕食它的生物的食物 LCA。
如果入度为 0,加入队列,并把这个结点连向其食物LCA,食物 LCA 为父结点。,并更新这个结点的各层倍增LCA。
拓扑排序结束后,开始建二号图。
根据上面加粗的字体连的边,建成一棵树,0 号结点为根。
每个结点的 “灾难值” 就是这个点的子树规模 - 1.(不算自己)
考虑
在
首先,
其次,
再次,
然后,
而
所以:
而后两个条件可以推出第一个条件。
在 DP 之前把
当然这就需要额外记录原来的编号,所以建议结构体。
所以来一个树状数组维护最大值,每次更新完就把
更新时查询一下小于
题意简述:
给定一棵
现在可以把一条边的边权改为 0 。
问:这
最大值最小,明摆着告诉你要二分。
那我们就二分,显然越小越好,越大越可行。
现在我们的任务就变成了判断最大值是
最大值是
又等价于:存在一条边删去后,最长的边边权小于等于
而一条边是公共边,就相当于这条边重复的次数就是路径条数。
这个东西可以用树上差分。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!