简单の暑假总结——倍增,RMQ问题

7.1 倍增

倍增呢,我认为应该算是一种思想,算不上一种算法

一般来讲,我们常将倍增和二进制思想结合,不仅可以很好的降低时间复杂度,还可以很好的降低空间复杂度

其实,我们在很久很久以前就已经接触了倍增和二进制思想,比如快速幂和多重背包的优化

倍增本身没有什么好说的,如果只考倍增,要么巨水,要么根本想不到要用倍增;如果不是只考倍增,那么一般是以一个对原算法的优化的面孔出现

所以,接下来我们将了解与倍增有关的问题——区间最值问题( RMQ 问题)

7.2 RMQ 问题

Eg 数列区间最大值

一句话概括:

现有一个长度为 N N N 的一个序列和 M M M 次询问,对于每一次询问,求出区间 [   l , r   ] [\ l,r\ ] [ l,r ] 的最大(小)值

先考虑暴力做法

7.2.1 RMQ 问题暴力求解

暴力应该是显而易见的,对于每一次询问,我们再来一重询问来求出该区间内的最大值并输出即可

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,x,y;
int a[100005];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}			//输入序列
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);			//输入询问
		int ans=-2147483647;
		for(int j=x;j<=y;j++){
			ans=max(ans,a[j]);			//遍历区间,求出最大值
		}
		printf("%d\n",ans);			//输出
	}
	return 0;
} 

但是,显然,这样的算法是 O ( n m ) O(nm) O(nm) ,而数据范围相当大: 1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 1 0 6 1\le n\le10^5,1\le m\le10^6 1n105,1m106

自然,超时不可避免的 QAQ

在这里插入图片描述

所以,我们需要更高级的做法

7.2.2 RMQ 问题倍增+DP

我们先考虑 DP 状态

定义状态:

d p [   i   ] [   j   ] dp[\ i\ ][\ j\ ] dp[ i ][ j ] 表示区间 [   i , i + 2 j − 1   ] [\ i,i+2^j-1\ ] [ i,i+2j1 ] 的最大值

那么显然,初始化 d p [   i   ] [   0   ] = a [   i   ] dp[\ i\ ][\ 0\ ]=a[\ i\ ] dp[ i ][ 0 ]=a[ i ]

结合下面的一张图,考虑状态转移方程式

在这里插入图片描述

我们考虑 1 1 1 号区间(即 d p [   1   ] [   1   ] dp[\ 1\ ][\ 1\ ] dp[ 1 ][ 1 ]),我们要求 d p [   1   ] [   1   ] dp[\ 1\ ][\ 1\ ] dp[ 1 ][ 1 ] 里的值,此时,因为我们已知了 d p [   1   ] [   0   ] dp[\ 1\ ][\ 0\ ] dp[ 1 ][ 0 ] d p [   2   ] [   0   ] dp[\ 2\ ][\ 0\ ] dp[ 2 ][ 0 ] 的值,而二者区间范围凑到一起刚好就是 d p [   1   ] [   1   ] dp[\ 1\ ][\ 1\ ] dp[ 1 ][ 1 ] 的区间范围,所以,我们比一个二者的最大值,即可得到 d p [   1   ] [   1   ] dp[\ 1\ ][\ 1\ ] dp[ 1 ][ 1 ] 的值

我们考虑 2 2 2 号区间(即 d p [   5   ] [   2   ] dp[\ 5\ ][\ 2\ ] dp[ 5 ][ 2 ]),我们要求 d p [   5   ] [   2   ] dp[\ 5\ ][\ 2\ ] dp[ 5 ][ 2 ] 里的值,此时,因为我们已知了 d p [   5   ] [   1   ] dp[\ 5\ ][\ 1\ ] dp[ 5 ][ 1 ] d p [   7   ] [   1   ] dp[\ 7\ ][\ 1\ ] dp[ 7 ][ 1 ] 的值,而二者区间范围凑到一起刚好就是 d p [   5   ] [   2   ] dp[\ 5\ ][\ 2\ ] dp[ 5 ][ 2 ] 的区间范围,所以,我们比一个二者的最大值,即可得到 d p [   5   ] [   2   ] dp[\ 5\ ][\ 2\ ] dp[ 5 ][ 2 ] 的值

此时,我们发现,对于区间 [   i , i + 2 j − 1   ] [\ i,i+2^j-1\ ] [ i,i+2j1 ] ,我们可以将其拆分为两部分, [   i , i + 2 j − 1 − 1   ] [\ i,i+2^{j-1}-1\ ] [ i,i+2j11 ] 以及 [   i + 2 j − 1 , i + 2 j − 1   ] [\ i+2^{j-1},i+2^j-1\ ] [ i+2j1,i+2j1 ] ,将两个小区间比上一个最大值,就是大区间的值

换言之,我们得到了状态转移方程式:

d p [   i   ] [   j   ] = max ⁡ ( d p [   i   ] [   j − 1   ] , d p [   i + 2 j − 1   ] [   j − 1   ] ) dp[\ i\ ][\ j\ ]=\max(dp[\ i\ ][\ j-1\ ],dp[\ i+2^{j-1}\ ][\ j-1\ ]) dp[ i ][ j ]=max(dp[ i ][ j1 ],dp[ i+2j1 ][ j1 ])

这样一来,我们就用 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的时间复杂度进行了预处理,接下来,处理每一次询问

假设现要求区间 [   l , r   ] [\ l,r\ ] [ l,r ] 的最大值,我们另设 k = log ⁡ 2 ( r − l + 1 ) k=\log_2(r-l+1) k=log2(rl+1) ,然后,求出 max ⁡ ( d p [   l   ] [   k   ] , d p [   r − 2 k + 1 ] [   k   ] ) \max(dp[\ l\ ][\ k\ ],dp[\ r-2^k+1][\ k\ ]) max(dp[ l ][ k ],dp[ r2k+1][ k ]) 即可得到答案

我们简单伪证一下

在这里插入图片描述

对于一个区间,我们只需要选择两个子区间,使其能够完全覆盖整个区间,再比较两个子区间的最大值即可

换言之,我们只需要得到 r − 2 k + 1 ≤ l + 2 k − 1 r-2^k+1\le l+2^k-1 r2k+1l+2k1 即可

先假设 r − 2 k + 1 > l + 2 k − 1 r-2^k+1>l+2^k-1 r2k+1>l+2k1

r − 2 k + 1 > l + 2 k − 1 r + 2 > l + 2 × 2 k r + 2 > l + 2 × ( r − l + 1 ) r + 2 > l + 2 × r − 2 × l + 2 r > 2 × r − l l > r \begin{aligned}r-2^k+1&>l+2^k-1\\r+2&>l+2\times2^k\\r+2&>l+2\times (r-l+1)\\r+2&>l+2\times r-2\times l+2\\r&>2\times r-l\\l&>r\end{aligned} r2k+1r+2r+2r+2rl>l+2k1>l+2×2k>l+2×(rl+1)>l+2×r2×l+2>2×rl>r

Tips: 2 k = n ( k = log ⁡ ( n ) ) 2^k=n(k=\log(n)) 2k=n(k=log(n))

显然,矛盾,假设不成立,所以 r − 2 k + 1 ≤ l + 2 k − 1 r-2^k+1\le l+2^k-1 r2k+1l+2k1

那么,现在,我们就可以得到完整代码了

#include<cmath>			//为了使用 log2 函数
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,x,y;
int dp[100005][105];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&dp[i][0]);			//初始化
	}
	for(int j=1;(1<<j)<=n;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){			//状态转移
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
		}
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);			//询问
		int k=log2(y-x+1);			//求解
		printf("%d\n",max(dp[x][k],dp[y-(1<<k)+1][k]));
	}
	return 0;
}

我的博客,完成啦哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值