Unverified Commit cc42f60d authored by ir1d's avatar ir1d Committed by GitHub
Browse files

Merge pull request #1434 from Ir1d/master

目录调整,移动 倍增 相关内容
parents 1476f970 bc7d0064
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
author: Ir1d, ShadowsEpic, Fomalhauthmj, siger-young, MingqiHuang, Xeonacid, hsfzLZH1

倍增法,通过字面意思来看就是翻倍。这个方法在很多算法中均有应用,其中最常用的就是 RMQ 问题和求 LCA 了。

## RMQ 问题

RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。

解决 RMQ 问题的主要方法有两种,分别是 ST 表和线段树,具体请参见[ST 表](/ds/sprase-table)页面和[线段树](/ds/segment)页面。

## 树上倍增求 LCA

具体请参见[最近公共祖先](/graph/lca/#_5)页面。
+6 −105
Original line number Diff line number Diff line
倍增法,通过字面意思来看就是翻倍。这个方法在很多算法中均有应用。其中最常用的就是 RMQ 问题和求 LCA 了。

## RMQ 问题

### 简介
## 简介

RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。

解决 RMQ 问题的主要方法有两种,分别是 ST 表和线段树。本文主要讲 ST 表。

### 引入
## 引入

[ST 表模板题](https://www.luogu.org/problemnew/show/P3865)

@@ -18,7 +14,7 @@ RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最

显然,这个算法会超时

### ST 表
## ST 表

 $ST$ 表基于倍增思想,可以做到 $O(n\log n)$ 预处理, $O(1)$ 回答每个询问。但是不支持修改操作。

@@ -50,7 +46,7 @@ RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最

显然,这两个区间会重叠。但是,重叠并不会对区间最大值产生影响。同时这两个区间刚好覆盖了 $[x,y]$ ,可以保证答案的正确性。

### 模板代码
## 模板代码

[ST 表模板题](https://www.luogu.org/problemnew/show/P3865)

@@ -96,7 +92,7 @@ int main() {
}
```

### 注意点
## 注意点

1.  输入输出数据一般很多,建议开启输入输出优化

@@ -109,105 +105,10 @@ Logn\left[i\right] &=Logn[\frac{i}{2}] + 1.
\end{aligned}\right.
$$

### 总结
## 总结

 $ST$ 表能较好的维护区间信息,时间复杂度较低,代码量相对其他算法不大。但是, $ST$ 表能维护的信息非常有限,不能较好地扩展,并且不支持修改操作。

## 树上倍增求 LCA

### LCA 简介

LCA(Least Common Ancestors)表示最近公共祖先。

对于一棵有根树,设 $LCA(u,v)=x$ ,则 $x$ 必须满足以下条件

-    $x$ 是 u 的祖先或 u

-    $x$ 是 v 的祖先或 v

-    $x$ 是在满足上面两个条件下深度最大的

显然,在一棵有根树内,对于任意两个节点有且仅有一个 $LCA$ 

解决这个问题,我们通常有以下方法

-   树上倍增(本文主要讲解此方法)

-   转化为 RMQ 问题

-   树链剖分

-   Tarjan

### 暴力做法

1.  将两个点跳到同一深度

    将深度大的点 **一步一步** 往上跳,发现另一个点是他的祖先,则另一个点就是 $LCA$ 

2.  一起往上跳

    当两个点深度一样但是还没有找到 LCA 的时候,就一起往 **一步一步** 上跳,知道跳到了同一个点。那么,这个点即为它们的 LCA

### 树上倍增

暴力慢的原因在于跳的时候是 **一步一步** 跳的,导致效率较低。如果我们可以 **一次跳多步** ,效率就大大提高了。

#### 预处理

令 $f[i][j]$ 表示 $i$ 的 $2^j$ 辈祖先,及从 $i$ 向根节点走 $2^j$ 步到达的节点。 $f[i][0]$ 就表示 $i$ 的父节点。

通过 $2^{j-1}\times 2^{j-1}=2^j$ 可以得到状态转移方程 $f[i][j]=f[f[i][j-1]][j-1]$ ~~(是不是和 $ST$ 的转移方程有点像)~~。自然,当 $i$ 没有 $2^j$ 辈祖先时 $f[i][j]=0$ 

一遍 DFS 计算即可

```cpp
void dfs(int u, int father) {
  dep[u] = dep[father] + 1;  // dep[x] 表示 x 的深度,在查询时会用到
  for (int i = 0; i <= 19; i++) f[u][i + 1] = f[f[u][i]][i];  // 预处理
  for (int i = first[u]; i; i = next[i])                      // 链式前向星
  {
    int v = go[i];
    if (v == father) continue;
    f[v][0] = u;  // f[v][0] 表示 v 的父亲
    dfs(v, u);
  }
}
```

#### 查询

依然采用暴力的思想。先将两个节点跳到同一深度,然后一起往上跳。

只不过在跳的过程中从一步一步跳变成了 **一次跳多步** 。可以具体分为以下几步

1.  让 $x$ 的深度比 $y$ 大(深度在预处理时已经求出)

2.  将两个节点跳到同一深度。在此处我们使用二进制思想,依次尝试向上跳 $2^i,2^{i-1}\cdots 2^1,2^0$ 。如果发现则 $x$ 跳到了 $y$ 就说明 $LCA(x,y)=y$ 

3.  一起往上跳。依然使用二进制思想,让他们一起往上跳 $2^i,2^{i-1}\cdots 2^1,2^0$ . 如果 $f[x][i]!=f[y][i]$ ,说明 $x$ 和 $y$ 还未相遇。最后, $x$ 和 $y$ 必定只差一步相遇。这时 $x$ 的父亲即 $f[x][0]$ 就是他们的 LCA

```cpp
int lca(int x, int y) {
  if (dep[x] < dep[y]) swap(x, y);  // 步骤 1
  for (int i = 20; i >= 0; i--)     // 步骤 2
  {
    if (dep[f[x][i]] >= dep[y]) x = f[x][i];
    if (x == y) return x;
  }
  for (int i = 20; i >= 0; i--)  // 步骤 3
    if (f[x][i] != f[y][i]) {
      x = f[x][i];
      y = f[y][i];
    }
  return f[x][0];
}
```

### 总结

树上倍增法可以在 $O(n\log n)$ 的时间内完成预处理,在 $O(\log n)$ 的时间里完成查询,是一个较高效的算法,代码量也不大,一般竞赛推荐使用。

## 练习

[RMQ 模板题](https://www.luogu.org/problemnew/show/P3865)
+6 −0
Original line number Diff line number Diff line
@@ -452,3 +452,9 @@ int main() {
  return 0;
}
```

## 习题

-   [严格次小生成树](https://www.luogu.org/problemnew/show/P4180)
-   [货车运输](https://www.luogu.org/problemnew/show/P1967)
-   [跑路](https://www.luogu.org/problemnew/show/P1613)

docs/intro/roadmap.md

0 → 100644
+1 −0
Original line number Diff line number Diff line

docs/lang/array.md

0 → 100644
+1 −0
Original line number Diff line number Diff line
Loading