前言
该文章同步于 c n b l o g s cnblogs cnblogs 发布。
在看这篇文章之前,请先自行了解以下几项东西:
-
1.倍增思想。
-
2.动态规划思想。
-
3.乘方位运算实现
如有错误,欢迎各位 dalao
批评指出。
什么是 s t st st 算法?
st 算法是一种解决 RMQ
问题的算法。RMQ
及 Range Minimum/Maximum Query
,即区间最大最小值查询。
该算法采用了 动态规划和倍增 的思想,预处理 O ( n l o g n ) O(nlogn) O(nlogn),查询 O ( 1 ) O(1) O(1),适用于一些查询较多的题目。
s t st st 算法具体实现
接下来给大家讲解如何实现 s t st st 算法。
我们假定是要求出 a a a 数组上的区间最大值,
我们定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从 i i i 开始,长度为 2 j 2^j 2j 的区间的最大值。
则对于初始值, d p [ i ] [ 0 ] = a [ i ] dp[i][0]=a[i] dp[i][0]=a[i],这个比较容易想到,即从 i i i 开始,长度为 1 1 1 的区间就是它本身,所以是 a [ i ] a[i] a[i]。
接着是状态转移。显然,对于一个长度为 2 j 2^j 2j 的区间,我们可以把它划分为两个长度为 2 j − 1 2^{j-1} 2j−1 的区间,然后再求最大值。则状态转移为: d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i + ( 1 < < ( j − 1 ) ) ] [ j − 1 ] ) dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]) dp[i][j]=max(dp[i][j−1],dp[i+(1<<(j−1))][j−1]),其中 1 < < ( j − 1 ) 1<<(j-1) 1<<(j−1),就表示 2 j − 1 2^{j-1} 2j−1 。
但是,当我们状态转移在枚举循环时,我们需要注意的是,我们要先枚举区间长度越小的在枚举区间长度越大的,所以 j j j 应该放在外层循环,而 i i i 就应该放在内层循环。
则此时,我们就可以先预处理出所有 d p [ i ] [ j ] dp[i][j] dp[i][j]。时间复杂度也就是 O ( n l o g n ) O(nlogn) O(nlogn)。
核心代码:
void init()//预处理函数
{
for(int i=1;i<=n;++i)
dp[i][0]=a[i];//初始值
for(int j=1;(1<<j)<=n;++j)//第二维j,注意 2^j 要小于n
{
for(int i=1;i+(1<<j)-1<=n;++i)//枚举i时要保证从i开始,有一个长度为 2^j 的区间时,不能超出 n。
{
dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)]][j-1]);//状态转移
}
}
}
最后就是如何查询的问题。
假设我们要查询的是 [ l , r ] [l,r] [l,r] 这个区间的区间最大值。我们可以发现,我们查询最大值的话,我们可以将 [ l , r ] [l,r] [l,r] 分成两个区间来查询,并且这两个区间总体合起来要包含 [ l , r ] [l,r] [l,r],且这两个区间可以不刚好包含,两个区间可以存在交集。
此时,我们可以定义一个数字 k k k,表示 log 2 r − l + 1 \log_2^{r-l+1} log2r−l+1(向下取整) ,则我们就可以根据 k k k,来求出我们要寻找的 [ l , r ] [l,r] [l,r] 的区间最大值,即求出 m a x ( d p [ l ] [ k ] , d p [ r − ( 1 < < k ) + 1 ] [ k ] ) max(dp[l][k],dp[r-(1<<k)+1][k]) max(dp[l][k],dp[r−(1<<k)+1][k]) 。这个查询的正确性也是十分明显,因为两个区间长度都是 2 k 2^k 2k,并且 2 k + 2 k = 2 k + 1 > ( r − l + 1 ) 2^k+2^k=2^{k+1}>(r-l+1) 2k+2k=2k+1>(r−l+1),所以这两个区间一定可以完全覆盖 [ l , r ] [l,r] [l,r]。
查询核心代码:
int rmq(int l,int r)
{
int k=log2(r-l+1);
return max(dp[l][k],dp[r-(1<<k)+1][k]);
}
可以发现, s t st st 算法是无法完成修改操作的,即使要去完成也效率过慢。但是对于只查询的题目来说, s t st st 算法一定是当之无愧的神!
s t st st 算法的一些扩展
s
t
st
st 算法是解决 RMQ
问题的,但是这种算法的思想我们却可以继续延伸下去。
s
t
st
st 算法之所以不能解决区间和是因为他在查询时,取
m
a
x
max
max 两个区间可能存在交集,也就是可能会重复计算。那么是不是可以重复计算的无修改的问题都可以运用
s
t
st
st 算法的思想解决呢?of course!
为了让大家更好理解,我这里先抛给大家一道例题,相信你做出来一定可以领会到 s t st st 算法思想的精髓所在。
显而易见,这个题目就是求区间 GCD
,并且我们可与发现,GCD
我们是可以重复计算的。举个例子,
g
c
d
(
10
,
5
)
=
g
c
d
(
10
,
10
,
5
)
gcd(10,5)=gcd(10,10,5)
gcd(10,5)=gcd(10,10,5)。
既然可以重复计算,那就肯定可以用 s t st st 算法的思想进行求解了!
我们只需要把 RMQ
问题中的
m
a
x
max
max 改为
g
c
d
gcd
gcd 就可以了。(代码中为了方便,我直接调用了 c++
的内置函数 __gcd
)
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[500005];
int dp[500005][35];
void init()
{
for(int j=1;(1<<j)<=n;++j)
{
for(int i=1;i+(1<<j)-1<=n;++i)
{
dp[i][j]=__gcd(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);//直接求 i~i+2^j-1 这个区间的gcd
}
}
}
int rmq(int l,int r)
{
int k=log2(r-l+1);
return __gcd(dp[l][k],dp[r-(1<<k)+1][k]);//我们只需要把 max 改为 __gcd 即可。
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]),dp[i][0]=a[i];
init();
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",rmq(l,r));
}
return 0;
}
除了在算法思想上的扩展,对于算法本身,我们也可以进行扩展。
例如 s t st st 算法本身其实是可以有二维形式的,例题:洛谷 P2216 [HAOI2007]理想的正方形
这个题目的话思路还是比较清晰的,至于具体代码实现请读者自己实践,如果实在做不出来就看一看题解吧。
后记
今天的算法就讲到这里,希望大家能有所收获,如有不懂的地方,也可以私信我。
my QQ number:1262264033
我们下次再见!