Loading docs/graph/lca.md +5 −5 Original line number Diff line number Diff line Loading @@ -26,11 +26,11 @@ ### 倍增算法 倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 `fa[x][i]` 数组,游标可以快速移动,大幅减少了游标跳转次数。 `fa[x][i]` 表示点 $x$ 的第 $2^i$ 个祖先。 `fa[x][i]` 数组可以通过 dfs 预处理出来。 倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 $\text{fa}_{x,i}$ 数组,游标可以快速移动,大幅减少了游标跳转次数。 $\text{fa}_{x,i}$ 表示点 $x$ 的第 $2^i$ 个祖先。 $\text{fa}_{x,i}$ 数组可以通过 dfs 预处理出来。 现在我们看看如何优化这些跳转: 在调整游标的第一阶段中,我们可以计算出 $u,v$ 两点的深度之差,设其为 $y$ 。通过将 $y$ 进行二进制拆分,我们将 $y$ 次游标跳转优化为 `count_one_in_binary_representation(y)` 次游标跳转。 在第二阶段中,我们从最大的 $i$ 开始循环尝试,一直尝试到 $0$ (包括 $0$ ),如果 `fa[u][i] != fa[v][i]` ,则令 `u = fa[u][i]; v = fa[v][i]` ,那么最后的 LCA 为 `fa[u][0]` 。 在调整游标的第一阶段中,我们要将 $u,v$ 两点跳转到同一深度。我们可以计算出 $u,v$ 两点的深度之差,设其为 $y$ 。通过将 $y$ 进行二进制拆分,我们将 $y$ 次游标跳转优化为「 $y$ 的二进制表示所含 `1` 的个数」次游标跳转。 在第二阶段中,我们从最大的 $i$ 开始循环尝试,一直尝试到 $0$ (包括 $0$ ),如果 $\text{fa}_{u,i}\not=\text{fa}_{v,i}$ ,则 $u\gets\text{fa}_{u,i},v\gets\text{fa}_{v,i}$ ,那么最后的 LCA 为 $\text{fa}_{u,0}$ 。 倍增算法的预处理时间复杂度为 $O(n \log n)$ ,单次查询时间复杂度为 $O(\log n)$ 。 另外倍增算法可以通过交换 `fa` 数组的两维使较小维放在前面。这样可以减少 cache miss 次数,提高程序效率。 Loading Loading @@ -224,7 +224,7 @@ Tarjan 算法需要初始化并查集,所以预处理的时间复杂度为 $O( ### 用欧拉序列转化为 RMQ 问题 对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 $2n-1$ 的序列,这个序列被称作这棵树的欧拉序列(可以看成是这棵树的一条欧拉回路所经过的节点构成的序列)。 对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 $2n-1$ 的序列,这个序列被称作这棵树的欧拉序列。 在下文中,把结点 $u$ 在欧拉序列中第一次出现的位置编号记为 $pos(u)$ (也称作节点 $u$ 的欧拉序),把欧拉序列本身记作 $E[1..2n-1]$ 。 Loading Loading @@ -252,7 +252,7 @@ Tarjan 算法需要初始化并查集,所以预处理的时间复杂度为 $O( for (int i = 1; i <= (N << 1); ++i) lg[i] = lg[i >> 1] + 1; for (int i = 1; i <= (N << 1) - 1; ++i) st[0][i] = dfn[i]; for (int i = 1; i <= lg[(N << 1) - 1]; ++i) for (int j = 1; j + (1 << n) - 1 <= ((N << 1) - 1); ++j) for (int j = 1; j + (1 << i) - 1 <= ((N << 1) - 1); ++j) st[i][j] = dep[st[i - 1][j]] < dep[st[i - 1][j + (1 << i - 1)] ? st[i - 1][j] : st[i - 1][j + (1 << i - 1)]; Loading Loading
docs/graph/lca.md +5 −5 Original line number Diff line number Diff line Loading @@ -26,11 +26,11 @@ ### 倍增算法 倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 `fa[x][i]` 数组,游标可以快速移动,大幅减少了游标跳转次数。 `fa[x][i]` 表示点 $x$ 的第 $2^i$ 个祖先。 `fa[x][i]` 数组可以通过 dfs 预处理出来。 倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 $\text{fa}_{x,i}$ 数组,游标可以快速移动,大幅减少了游标跳转次数。 $\text{fa}_{x,i}$ 表示点 $x$ 的第 $2^i$ 个祖先。 $\text{fa}_{x,i}$ 数组可以通过 dfs 预处理出来。 现在我们看看如何优化这些跳转: 在调整游标的第一阶段中,我们可以计算出 $u,v$ 两点的深度之差,设其为 $y$ 。通过将 $y$ 进行二进制拆分,我们将 $y$ 次游标跳转优化为 `count_one_in_binary_representation(y)` 次游标跳转。 在第二阶段中,我们从最大的 $i$ 开始循环尝试,一直尝试到 $0$ (包括 $0$ ),如果 `fa[u][i] != fa[v][i]` ,则令 `u = fa[u][i]; v = fa[v][i]` ,那么最后的 LCA 为 `fa[u][0]` 。 在调整游标的第一阶段中,我们要将 $u,v$ 两点跳转到同一深度。我们可以计算出 $u,v$ 两点的深度之差,设其为 $y$ 。通过将 $y$ 进行二进制拆分,我们将 $y$ 次游标跳转优化为「 $y$ 的二进制表示所含 `1` 的个数」次游标跳转。 在第二阶段中,我们从最大的 $i$ 开始循环尝试,一直尝试到 $0$ (包括 $0$ ),如果 $\text{fa}_{u,i}\not=\text{fa}_{v,i}$ ,则 $u\gets\text{fa}_{u,i},v\gets\text{fa}_{v,i}$ ,那么最后的 LCA 为 $\text{fa}_{u,0}$ 。 倍增算法的预处理时间复杂度为 $O(n \log n)$ ,单次查询时间复杂度为 $O(\log n)$ 。 另外倍增算法可以通过交换 `fa` 数组的两维使较小维放在前面。这样可以减少 cache miss 次数,提高程序效率。 Loading Loading @@ -224,7 +224,7 @@ Tarjan 算法需要初始化并查集,所以预处理的时间复杂度为 $O( ### 用欧拉序列转化为 RMQ 问题 对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 $2n-1$ 的序列,这个序列被称作这棵树的欧拉序列(可以看成是这棵树的一条欧拉回路所经过的节点构成的序列)。 对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 $2n-1$ 的序列,这个序列被称作这棵树的欧拉序列。 在下文中,把结点 $u$ 在欧拉序列中第一次出现的位置编号记为 $pos(u)$ (也称作节点 $u$ 的欧拉序),把欧拉序列本身记作 $E[1..2n-1]$ 。 Loading Loading @@ -252,7 +252,7 @@ Tarjan 算法需要初始化并查集,所以预处理的时间复杂度为 $O( for (int i = 1; i <= (N << 1); ++i) lg[i] = lg[i >> 1] + 1; for (int i = 1; i <= (N << 1) - 1; ++i) st[0][i] = dfn[i]; for (int i = 1; i <= lg[(N << 1) - 1]; ++i) for (int j = 1; j + (1 << n) - 1 <= ((N << 1) - 1); ++j) for (int j = 1; j + (1 << i) - 1 <= ((N << 1) - 1); ++j) st[i][j] = dep[st[i - 1][j]] < dep[st[i - 1][j + (1 << i - 1)] ? st[i - 1][j] : st[i - 1][j + (1 << i - 1)]; Loading