主要应用场景
RMQ:区间最值问题
LCA:最近公共祖先问题
RMQ问题——区间最值
如果用数组f[N]存储,用数组a[i][j]表示从第i个数起连续 2^j 个数中的最大值,[i,i + 2^j - 1]
,显然a[i][0] = f[i],则很容易得到状态转移方程:
a[i][j] = max(a[i][j - 1], a[i + 2^(j - 1)][j - 1])
那么我们只需要对开始时,对于数组a进行预处理。我们先更新所有长度为0的情况a[i][0] = f[i]。然后,通过2个1元素的最值a[i][0]获得1个2元素的最值a[i][1],依次类推。
由于这里第二维j表示的是从i起,长度为2^j个数,所以j最大为logn,这里的复杂度应该是O(nlogn)。
此时,对于每一次询问的复杂度为O(1)
如果我们查询区间为[l,r],那么此时区间长度为 (r - l + 1),所以取 k = log(r - l + 1),可以将询问转换成 max(l,r) = max(a[l][k], a[r - (1 << k) + 1][k])。
其中,a[l][k]表示的是区间[l, l + 2^k - 1], a[r - 2^k + 1][k]表示的区间是[r - 2^k + 1, r]。
参考代码
参考例题:洛谷 P3865 【模板】ST 表
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 3;
int n, t, arr[N][32];int query(int l, int r)
{int k = (int)(log((r - l + 1) * 1.0) / log(2.0));return max(arr[l][k], arr[r - (1 << k) + 1][k]);
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> t;for (int i = 1; i <= n; ++i)cin >> arr[i][0];for (int j = 1; j <= (int)(log(n * 1.0) / log(2.0)); ++j){for (int i = 1; i + (1 << j) - 1 <= n; ++i){arr[i][j] = max(arr[i][j - 1], arr[i + (1 << (j - 1))][j - 1]);}}while (t--){int l, r;cin >> l >> r;cout << query(l, r) << '\n';}
}
LCA问题——最近公共祖先
参考例题:
- 洛谷 P3379 【模板】最近公共祖先(LCA)
- 蓝桥OJ 最近公共祖先LCA查询
要求两个点的LCA,先将这两个点调整到同一高度(结点高度大的向上跳),之后两个结点同时往上跳,当他们到达同一高度时,该结点即为他们的LCA。
这里关键的是预处理,将每个结点的向上的父节点全部记录下来,方便结点向上跳时候使用。
例1参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
// 2^logn 需要大于 N
const int N = 500003, logn = 22;
struct zzz {int t, nex;
}e[N << 1];
int head[N], tot;
void add(int x, int y) {e[++tot].t = y;e[tot].nex = head[x];head[x] = tot;
}
int depth[N], fa[N][logn], lg[N];
void dfs(int now, int fath) {fa[now][0] = fath; depth[now] = depth[fath] + 1;for(int i = 1; i <= lg[depth[now]]; ++i)fa[now][i] = fa[fa[now][i-1]][i-1];for(int i = head[now]; i; i = e[i].nex)if(e[i].t != fath) dfs(e[i].t, now);
}
int LCA(int x, int y) {if(depth[x] < depth[y]) swap(x, y);while(depth[x] > depth[y])x = fa[x][lg[depth[x]-depth[y]] - 1];if(x == y) return x;for(int k = lg[depth[x]] - 1; k >= 0; --k)if(fa[x][k] != fa[y][k])x = fa[x][k], y = fa[y][k];return fa[x][0];
}
int main() {int n, m, s; scanf("%d%d%d", &n, &m, &s);// 建树for(int i = 1; i <= n-1; ++i) {int x, y; scanf("%d%d", &x, &y);add(x, y); add(y, x);}// 预处理for(int i = 1; i <= n; ++i)lg[i] = lg[i-1] + (1 << lg[i-1] == i);dfs(s, 0);for(int i = 1; i <= m; ++i) {int x, y; scanf("%d%d",&x, &y);printf("%d\n", LCA(x, y));}return 0;
}
例2参考代码
#include <bits/stdc++.h>
using namespace std;
// 2^logn 需要大于 N
const int N = 1e5 + 3, logn = 20;
class TreeEdge
{
public:int to, nex;
} edge[N << 1];
// head
int head[N], tot;
void add(int x, int y)
{edge[++tot].to = y;edge[tot].nex = head[x];head[x] = tot;
}
int n, q, depth[N], f[N][logn], lg[N];void dfs(int now, int fath)
{f[now][0] = fath, depth[now] = depth[fath] + 1;for (int i = 1; i <= lg[depth[now]]; ++i)f[now][i] = f[f[now][i - 1]][i - 1]; // 2^(i - 1) * 2^(i - 1) = 2^ifor (int i = head[now]; i; i = edge[i].nex)if (edge[i].to != fath)dfs(edge[i].to, now);
}int lca(int a, int b)
{if (depth[a] < depth[b])swap(a, b);while (depth[a] > depth[b])a = f[a][lg[depth[a] - depth[b]] - 1];if (a == b)return a;for (int k = lg[depth[a]] - 1; k >= 0; --k)if (f[a][k] != f[b][k])a = f[a][k], b = f[b][k];return f[a][0];
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n;for (int i = 1; i < n; ++i){int u, v;cin >> u >> v;add(u, v), add(v, u);}for (int i = 1; i <= n; ++i)lg[i] = lg[i / 2] + 1;dfs(1, 0);cin >> q;while (q--){int a, b;cin >> a >> b;cout << lca(a, b) << '\n';}
}