倍增算法笔记

主要应用场景

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])

倍增算法笔记_第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 
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问题——最近公共祖先

参考例题:

  1. 洛谷 P3379 【模板】最近公共祖先(LCA)
  2. 蓝桥OJ 最近公共祖先LCA查询

要求两个点的LCA,先将这两个点调整到同一高度(结点高度大的向上跳),之后两个结点同时往上跳,当他们到达同一高度时,该结点即为他们的LCA。
这里关键的是预处理,将每个结点的向上的父节点全部记录下来,方便结点向上跳时候使用。

例1参考代码(C++)

#include 
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 
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^i
    for (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';
    }
}

你可能感兴趣的:(算法学习,算法,笔记,c++,蓝桥杯)