【基础算法】倍增和ST表

倍增

简单介绍

  • 倍增算法,顾名思义,就是成倍地增加。它能将线性的处理时间优化为对数级处理时间,大大降低了时间复杂度。它被运用于很多算法中,如本文介绍的ST表和后面会讲到的LCA。

示例一

  • 小明有无数枚不确定面额的纸币,他会在使用一张纸币的时候确定它的面额。现在给定一个数n,小明希望用最少数量的纸币表示[1,n]中任意一个数。
  • 很显然,每张纸币面额为1即可。但是,这样数量是不是太多了?那是不是可以将部分纸币的面额翻倍,变成2?那面额为2的纸币是不是可以翻倍为4?
  • 以此类推,我们发现:只要选择1,2,4,……,2^i的面额的纸币,我们就可以表示[1,2 ^(i+1)-1]内的所有数,i=log(n+1)-1。此时所需纸币数量最少。

示例二

  • 现有一条链,编号从1-N。多次查询:与编号为i的点的距离为L的点的编号是什么?(为了方便,我们定义一个点到自身距离为1
  • 很容易想到一个做法:g[i][j]。i表示编号,j表示距离。g[i][j]则表示与i点距离为j的点的编号。但是这个做法有缺陷:距离不能过长,否则会MLE或者TLE。
  • 我们可以运用倍增算法,更改g[i][j]的定义为与i点距离为2^j的点的编号。那么我们如何求解该数组呢?
    1. 当j=0时,距离为1,g[i][j]=i
    2. 当j>0时,g[i][j]=g[g[i][j-1]][j-1]。解释:根据倍增算法流程,当我们求解g[i][j]时,g[i][j-1]显然已经被求解。此时,我们相当于从i走到x(x与i的距离为2^(j-1)),再从x走到y(y与x距离为2 ^(j-1),y与i的距离为2 ^j),即将一段距离拆分为两段相同的距离。
  • 代码
    for (int i = 1; i <= n; i++) g[i][0]=i;
    for (int i = 1; i <= 20; i++) {
    	for (int j = 1; j <= n; j++) {
    		g[j][i] = g[g[j][i - 1]][i - 1];
    	}
    }
    

ST表(静态RMQ)

简单介绍

  • 主要用于解决可重复贡献问题。
  • 例如求编号1-10的元素的最大值,我们可以先求前6个数的最大值,再求后8个数的最大值,最后两个值取MAX即可得到正确答案,尽管中间的元素被重复计算了。
  • 常见的可重复贡献问题有:区间最值、区间GCD等。

构建ST表的方法

  • 这里以构建区间最大值为例。
    1. 我们定义st[i][j]表示以i为起点,长度为2^j-1的区间最大值,即[i,i+2 ^j-1]的最大值;
    2. 初始状态:显然st[i][0]=arr[i]
    3. 倍增:由倍增思想可知,要求长度为2^j的区间最大值,可以先求长度为2 ^-1的两段区间最大值再取MAX。由此可以得出st[i][j]=max(st[i][j-1],st[i+2 ^(j-1)][j-1]
    4. 综上所述,我们可以先枚举区间长度,再枚举区间起点即可构建出ST表。

查询ST表

  • Q:我们在构建ST表的时候,区间的长度均为2的倍数(或者是1),那如果我们遇见区间长度不是2的倍数怎么办?
  • A:例如查询长度为r-l+1,(r-l+1)%2!=0。我们将公式2^j=(r-l+1)转化得到:j=log(r-l+1),此时j不是整数。我们可以将j向下取整,得到区间[l,l+2^j-1]。对于右边缺失的一部分,我们可以用一个新区间表示:[r-2 ^j+1,r]。因为l+2 ^j-1>=r-2 ^j+1,所以根据可重复贡献,两段有重叠的区间可以得到所求区间的最大值。

代码

#include <iostream>
#include <cmath>

using namespace std;

const int N = 1000010;
int f[N][64];
int n, m;
inline int read()
{
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
	return x * f;
}
inline void init(int n)
{
	for (int i = 1; i <= n; i++) f[i][0] = read();
	for (int j = 1; j <= 21; j++) {
		for (int i = 1; i + (1 << j) - 1 <= n; i++) f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
	}
}
inline int query(int l, int r)
{
	int x = log2(r-l+1);
	return max(f[l][x], f[r - (1 << x) + 1][x]);
}
int main()
{
	n = read(); m = read();
	init(n);
	
	for (int i = 1; i <= m; i++) {
		int l, r;
		l = read(), r = read();
		printf("%d\n", query(l, r));
	}
	return 0;
}

END

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值