谨以此题纪念我的第一棵线段树…qwq
【BHOJ 1623】RMQ问题
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 1≤L≤R≤N≤200000,M≤10000
输出
对于每次询问,输出当前查询区间的最大值
输入样例
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 < n ) dp[0][k] = a[k] (0≤k<n) dp[0][k]=a[k](0≤k<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[i−1][j],dp[i−1][j+2i−1])。
- 即 [ 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+2i−1)⋃[j+2i−1,j+2i−1+2i−1) 中的最大值。 -
递推顺序:外层循环区间宽度系数 [ 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,N−2i−1)。
- 这样在每次内层最后一次循环时,区间上界都能取到 N + 2 i − 1 − 1 N+2^{i-1}-1 N+2i−1−1,肯定是能覆盖到 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+1−2kk]),其中 k k = log 2 ( R − L + 1 ) kk=\log_2(R-L+1) kk=log2(R−L+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+1−2kk]) 为 [ 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+1−2kk,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+2kk≥R+1−2kk,即 2 k k ≥ R − L + 1 2 2^{kk}\ge \frac{R-L+1}{2} 2kk≥2R−L+1
- 而 k k = log 2 ( R − L + 1 ) kk=\log_2(R-L+1) kk=log2(R−L+1) 时有 2 k k ∈ ( R − L + 1 2 , R − L + 1 ] 2^{kk} \in (\frac{R-L+1}{2},\ R-L+1] 2kk∈(2R−L+1, R−L+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+1−2kk]) 就是 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,零点的,加油鸭