【BHOJ RMQ问题】RMQ | (倍增)ST动态规划 | 线段树 | E

本文详细介绍了如何使用动态规划(基于倍增思想和ST表)和线段树解决RMQ(区间最值查询)问题。通过实例分析了两种方法的时间和空间复杂度,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

谨以此题纪念我的第一棵线段树…qwq

【BHOJ 1623】RMQ问题

时间限制: 1000 ms 内存限制: 65536 kb
总通过人数: 137 总提交人数: 156

Tags:RMQ问题 动态规划 倍增 ST表 线段树

题目描述

N N N个数, M M M次询问。每次询问输入两个数字 L L L, R R R,输出 L L L R R R区间内的最大数。

输入

一组。第一行输入 N N N,接下来一行 N N N个数。接下来输入 M M M,接下来 M M M行每行输入 L L L R R R代表一次询问

范围: 1 ≤ L ≤ R ≤ N ≤ 200000 , M ≤ 10000 1 ≤ L ≤ R ≤ N ≤ 200000, M ≤ 10000 1LRN200000,M10000


输出

对于每次询问,输出当前查询区间的最大值

输入样例

6 
34 1 8 123 3 2 
4 
1 2 
1 5 
3 4 
2 3 

输出样例

34 
123 
123 
8 

分析

最基础的 R M Q RMQ RMQ问题,没有更新,只有多次查询区间最值

先分析动态规划解法:(利用倍增的思想和ST表
  • 定义: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示数组下标区间 [ j , j + 2 i ) [j,j+2^i) [j,j+2i) 中的最大值。

  • 维度:第一维决定区间宽度,第二维是区间起点。

  • 初始化: d p [ 0 ] [ k ] = a [ k ] ( 0 ≤ k &lt; n ) dp[0][k] = a[k] (0≤k&lt;n) dp[0][k]=a[k](0k<n)。原因很显然, d p [ 0 ] [ k ] dp[0][k] dp[0][k] 表示下标区间 [ k , k + 2 0 ) [k, k+2^0) [k,k+20) 中的最大值,那就是它自己嘛。

  • 状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j + 2 i − 1 ] ) dp[i][j]=max(dp[i−1][j],dp[i−1][j+2^{i−1}]) dp[i][j]=max(dp[i1][j],dp[i1][j+2i1])
    - 即 [ j , j + 2 i ) [j,j+2^i) [j,j+2i) 中的最大值是 [ j , j + 2 i − 1 ) ⋃ [ j + 2 i − 1 , j + 2 i − 1 + 2 i − 1 ) [j,j+2^{i−1}) \bigcup [j+2^{i−1},j+2^{i−1}+2^{i−1}) [j,j+2i1)[j+2i1,j+2i1+2i1) 中的最大值。

  • 递推顺序:外层循环区间宽度系数 [ 1 , ( i n t ) log ⁡ 2 N ] [1,(int)\log_2N] [1,(int)log2N],内层循环第二维区间起点 [ 0 , N − 2 i − 1 ) [0,N-2^{i-1}) [0,N2i1)
    - 这样在每次内层最后一次循环时,区间上界都能取到 N + 2 i − 1 − 1 N+2^{i-1}-1 N+2i11,肯定是能覆盖到 N N N的,所以不用担心上界不够大。

  • R M Q RMQ RMQ 查询: a n s = m a x ( d p [ k k ] [ L ] , d p [ k k ] [ R + 1 − 2 k k ] ) ans=max(dp[kk][L], dp[kk][R+1-2^{kk}]) ans=max(dp[kk][L],dp[kk][R+12kk]),其中 k k = log ⁡ 2 ( R − L + 1 ) kk=\log_2(R-L+1) kk=log2(RL+1)
    - 据 d p dp dp 定义, m a x ( d p [ k k ] [ L ] , d p [ k k ] [ R + 1 − 2 k k ] ) max(dp[kk][L], dp[kk][R+1-2^{kk}]) max(dp[kk][L],dp[kk][R+12kk]) [ L , L + 2 k k ) ⋃ [ R + 1 − 2 k k , R + 1 ) [L,L+2^{kk})\bigcup[R+1-2^{kk}, R+1) [L,L+2kk)[R+12kk,R+1) 中的最大值
    - 因此,为了完全覆盖住 [ L , R + 1 ) [L, R+1) [L,R+1),需要 L + 2 k k ≥ R + 1 − 2 k k L+2^{kk}\ge R+1-2^{kk} L+2kkR+12kk,即 2 k k ≥ R − L + 1 2 2^{kk}\ge \frac{R-L+1}{2} 2kk2RL+1
    - 而 k k = log ⁡ 2 ( R − L + 1 ) kk=\log_2(R-L+1) kk=log2(RL+1) 时有 2 k k ∈ ( R − L + 1 2 ,   R − L + 1 ] 2^{kk} \in (\frac{R-L+1}{2},\ R-L+1] 2kk(2RL+1, RL+1],满足上式
    - 所以 m a x ( d p [ k k ] [ L ] , d p [ k k ] [ R + 1 − 2 k k ] ) max(dp[kk][L], dp[kk][R+1-2^{kk}]) max(dp[kk][L],dp[kk][R+12kk]) 就是 a n s ans ans

  • 时间复杂度:预处理 O ( N l o g N ) O(NlogN) O(NlogN),查询 Θ ( M ) \Theta(M) Θ(M),总 O ( N l o g N ) O(NlogN) O(NlogN)

  • 空间复杂度: Θ ( N l o g N ) \Theta(NlogN) Θ(NlogN)


然后分析线段树的解法:
  • 其实很简单…连更新都没有,只要初始化和查询就行了。
  • 时间复杂度:建树 Θ ( N ) \Theta(N) Θ(N),查询 O ( M l o g N ) O(MlogN) O(MlogN),总 O ( M l o g N ) O(MlogN) O(MlogN)
  • 空间复杂度: Θ ( N ) \Theta(N) Θ(N)



AC代码


首先是动态规划解法的代码:
#include <cstdio>
#include <cmath>

#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define se(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;else if(_c==-1)return 0;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define PC putchar
void PRT(const int a){if(a<0){putchar(45),PRT(-a);return;}if(a>=10)PRT(a/10);putchar(a%10+48);}

#define MN 200007
#define log2n(x) (log(x)/0.693147180560)
#define MAX(a,b) (a>b?a:b)

int dp[(int)log2n(MN) + 3][MN];
constexpr int POW2[]{0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000, 0x20000000, 0x40000000};

int main()
{
	int n;
	sc(n)
	for (int left=0; left<n; ++left)
		sc(dp[0][left])

	int kk = (int)log2n(n);
	for (int k=1; k<=kk; ++k)
	{
		int max_left = n-POW2[k-1];
		for (int left=0; left<max_left; ++left)
		{
			dp[k][left] = MAX(dp[k-1][left], dp[k-1][left + POW2[k-1]]);
		}
	}

	int q;
	sc(q)
	while (q--)
	{
		int l, r;
		sc(l)sc(r)--l,--r;	// 因为预处理时下标是从0开始的,而询问是从1开始的
		int kk = (int)log2n(r-l+1);
		PRT(MAX(dp[kk][l], dp[kk][r+1-POW2[kk]])), PC(10);
	}
}

然后是线段树解法的代码:
#include <stdio.h>
#include <string.h>
#include <climits>

#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define PC putchar
#define MAX(a, b) ((a)>(b)?(a):(b))
template<typename T>void PRT(const T a){if(a<0){PC(45),PRT(-a);return;}if(a>=10)PRT(a/10);PC(a%10+48);}
template<typename T>void UPRT(const T a){if(a>=10)UPRT(a/10);PC(a%10+48);}

constexpr int MN(2e5+3);

int a[MN];

template <const int MN, typename VType = int, typename SType = int>
class STree
{
	private:

		struct Node
		{
			int l, r;
			VType max;
			SType lazy;
		} t[4 * MN];

	public:

#define update_node(i, v) \
	do \
	{ \
		t[i].max += v, \
		t[i].lazy += v; \
	} while (0)

#define push_up(i, li, ri) \
	t[i].max = MAX(t[li].max, t[ri].max)

#define push_down(i, li, ri) \
	do \
	{ \
		int lz = t[i].lazy; \
		if (lz) \
		{ \
			update_node(li, lz); \
			update_node(ri, lz); \
			t[i].lazy = 0; \
		} \
	} while (0)

		void init(const int l, const int r, const int i = 1)
		{
			t[i].l = l, t[i].r = r,
			t[i].max =
			t[i].lazy = 0;
			if (l == r)
				t[i].max = a[l];
			else
			{
				int mid = (l+r)>>1, li = i+i, ri = li+1;
				init(l, mid, li);
				init(mid+1, r, ri);
				push_up(i, li, ri);
			}
		}

		VType get_max(const int l, const int r, const int i = 1)
		{
			if (l <= t[i].l && t[i].r <= r)
				return t[i].max;
			else
			{
				int li = i+i, ri = li+1;
				push_down(i, li, ri);
				int mid = (t[i].l + t[i].r)>>1;
				VType ans1 = INT_MIN, ans2 = INT_MIN;
				if (l <= mid)
					ans1 = get_max(l, r, li);
				if (r > mid)
					ans2 = get_max(l, r, ri);
				return MAX(ans1, ans2);
			}
		}
};

STree<MN, int, int> stree;
int main()
{
	int n;
	sc(n)
	for (int i=1; i<=n; ++i)
		sc(a[i])
	stree.init(1, n);
	int q;
	sc(q)
	while (q--)
	{
		int l, r;
		sc(l)sc(r)
		PRT(stree.get_max(l, r)), PC(10);
	}
	
	return 0;
}

第一篇用 `MarkDown` 编辑器写的博客,感觉还不错~

今晚还有场 div2,零点的,加油鸭

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值