RMQ学习笔记
前言:这个算法无论是从适配性还是长度来说都很有实力…💦
关于 RMQ
RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。
详细信息
求 l − r l-r l − r 区间内的最大/最小数.
区间构造
本质是DP.设 f [ i ] [ j ] f[i][j] f [ i ] [ j ] 为 i ∼ i + 2 j − 1 i\sim i+2^{j-1} i ∼ i + 2 j − 1 的区间最大值.特别地,f [ i ] [ 0 ] = a [ i ] f[i][0]=a[i] f [ i ] [ 0 ] = a [ i ] .(一个数的最大值是它本身).
状态转移方程: f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1]) f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) .
区间查询
设要查询的区间为 [ L , R ] [L,R] [ L , R ] (包括LR)
k = l o g 2 ( R − L + 1 ) k = log2(R-L+1)
k = l o g 2 ( R − L + 1 )
r e s [ L ] [ R ] = m a x ( f [ L ] [ K ] , f [ R − 2 k + 1 ] [ k ] ) res[L][R] = max(f[L][K],f[R-2^k+1][k])
r e s [ L ] [ R ] = m a x ( f [ L ] [ K ] , f [ R − 2 k + 1 ] [ k ] )
注意:为保证精度,请自己推导 2 k 2^k 2 k ,而不是直接使用 l o g log l o g 函数.
时间复杂度: O ( n l o g 2 n ) O(nlog_2n) O ( n l o g 2 n )
A. 超级记忆力
题目描述
小A同学拥有无与伦比的超级记忆力,他可以一次性记住很多数字。
为了考验一下小A同学的记忆力,王老师一次性给小A展示了 N N N 个整数。然后问了他 M M M 个问题,每个问题给定一个区间,要求小A同学说出这个区间中的最大数是多少?
为方便老师检验小A同学的答案是否正确,请你先编程求出正确的答案。
输入
第一行两个整数 N , M N,M N , M 表示数字的个数和要询问的次数;
接下来一行为 N N N 个数;
接下来 M M M 行,每行都有两个整数 X , Y X,Y X , Y 表示询问的区间。
数据范围:
1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 1 0 6 , 1 ≤ X ≤ Y ≤ N 1≤N≤10^5,1≤M≤10^6,1≤X≤Y≤N 1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 1 0 6 , 1 ≤ X ≤ Y ≤ N 。数字不超过 C/C++ 的 int 范围。
解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 , L = 20 ;int a[N], f[N][L];int lg[N];int n, m, x, y, k;int main () { scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) lg[i] = lg[i / 2 ] + 1 ; for (int j = 0 ; j < L; j++) { for (int i = 1 ; i + (1 << j) - 1 <= n; i++) { if (j == 0 ) f[i][j] = a[i]; else f[i][j] = max (f[i][j - 1 ], f[i + (1 << j - 1 )][j - 1 ]); } } while (m--) { scanf ("%d%d" , &x, &y); k = lg[y - x + 1 ]; printf ("%d\n" , max (f[x][k], f[y - (1 << k) + 1 ][k])); } }
B. 荣耀之战
题目描述
小 A A A 是一名游戏玩家,他正在玩一款叫做“荣耀之战”的游戏。在这个游戏中,他需要通过完成任务来提升自己的等级。
游戏地图上有 N N N 个排成一排的装备,每个装备都标注好了经验值和危险值,第 i i i 个装备的经验值为 V i V_i V i 危险值为 D i D_i D i 。
小 A A A 接到了一个任务,他需要在这 N N N 个装备中,选取连续的 若干个装备,并使得这些装备的经验值总和不小于 K K K (≥ K \ge K ≥ K )。同时要使得这些装备的最大的危险值尽可能的小。
请编程计算出,满足题意的方案中,最大的危险值最小是多少?
输入
第 1 1 1 行读入 2 2 2 个整数 N , K N,K N , K 。
接下来 N N N 行,每行读入 2 2 2 个整数 V i , D i V_i,D_i V i , D i ,分别表示每个装备的经验值和危险值。
数据范围
对于 100 % 100\% 1 0 0 % 的数据,满足 1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1 ≤ N ≤ 1 0 5 ,1 ≤ V i , D i ≤ 1 0 9 1 \le V_i,D_i \le 10^9 1 ≤ V i , D i ≤ 1 0 9 ,1 ≤ K ≤ 1 0 18 1 \le K \le 10^{18} 1 ≤ K ≤ 1 0 1 8 。
要选出一个区间,使得他们在 V i ≥ K V_i\ge K V i ≥ K 的情况下 D i D_i D i 的总和最小.能够转化为: 选择 V l + V i + 1 + . . . + V r ≥ K V_l+V_{i+1}+...+V_r \ge K V l + V i + 1 + . . . + V r ≥ K 且 $D_l+D_{i+1}+…+D_r $ 最小.很容易想到用双指针来解决.注意:一定要让已经过去的数出队!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 ;const int L = 20 ;long long a[N];long long lg[N], f[N][L];long long n, m, v[N], w[N];#define value long long value max (value a, value b) { return a > b ? a : b; } value min (value a, value b) { return a < b ? a : b; } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) { scanf ("%lld%lld" , &v[i], &w[i]); } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) { lg[i] = lg[i / 2 ] + 1 ; } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) f[j][i] = w[j]; else f[j][i] = max (f[j][i - 1 ], f[j + (1 << (i - 1 ))][i - 1 ]); } } long long r = 0 , sum = 0 ; long long ans = INT_MAX; for (int l = 1 ; l <= n; l++) { while (r + 1 <= n and sum < m) { sum += v[++r]; } if (sum >= m) { long long len = lg[r - l + 1 ]; ans = min (ans, max (f[l][len], f[r - (1 << len) + 1 ][len])); } sum -= v[l]; } printf ("%lld\n" , ans); }
C.异或
题目内容
有一个长度为 N N N 的数列 A 1 , A 2 , … , A n A_1,A_2,\dots,A_n A 1 , A 2 , … , A n 。
请问该数列中任意取一个区间 [ L , R ] [L,R] [ L , R ] 中,是否存在 2 2 2 个数,使得这两个数异或的结果为 T T T 。
请注意,本题会发起 M M M 次询问,对于每次询问的区间,如果能找到符合题意的数对,请输出 yes,否则请输出 no。
输入
第一行包含三个整数 n , m , T n, m, T n , m , T 。
第二行包含 n n n 个整数,数字之间用空格隔开。
接下来的 M M M 行,每行有一个询问区间,每个询问区间包含 2 2 2 个整数 L , R L,R L , R 。
数据规模
对于 20 % 20 \% 2 0 % 数据, 1 ≤ n , m ≤ 100 1 \leq n, m \leq 100 1 ≤ n , m ≤ 1 0 0 ;
对于另外 40 % 40 \% 4 0 % 数据, 1 ≤ n , m ≤ 1000 1 \leq n, m \leq 1000 1 ≤ n , m ≤ 1 0 0 0 ;
对于 100 100 % 1 0 0 的数据, 1 ≤ n , m ≤ 1 0 5 , 0 ≤ T < 2 20 , 1 ≤ L ≤ R ≤ n , 0 ≤ a i ≤ 2 20 1 \leq n, m \leq 10^5,0 \leq T < 2^{20},1\le L \le R \le n,0 \le a_i \le 2^{20} 1 ≤ n , m ≤ 1 0 5 , 0 ≤ T < 2 2 0 , 1 ≤ L ≤ R ≤ n , 0 ≤ a i ≤ 2 2 0
思路
首先,我们暴力肯定过不了.
题目要求给定区间内是否有一对数.a , b a,b a , b 满足 a b = T a^b=T a b = T
简单推到可得: a T = b a^T=b a T = b 是由上面式子转化而来的.
我们随便列一组数据,并存储到 a a a 数组里面: a [ ] = { 2 , 1 , 3 , 4 , 2 , 3 , 2 , 3 } a[]=\{2,1,3,4,2,3,2,3\} a [ ] = { 2 , 1 , 3 , 4 , 2 , 3 , 2 , 3 }
然后没个数异或 T T T 得: a 2 [ ] = { 3 , 0 , 2 , 5 , 3 , 2 , 3 , 2 } a_2[]=\{3,0,2,5,3,2,3,2\} a 2 [ ] = { 3 , 0 , 2 , 5 , 3 , 2 , 3 , 2 }
然后在每个数的前面查找异或 T T T 后的数,如果找不到标记为 0 0 0 :a 3 [ ] = { 0 , 0 , 1 , 0 , 3 , 5 , 6 , 7 } a_3[]=\{0,0,1,0,3,5,6,7\} a 3 [ ] = { 0 , 0 , 1 , 0 , 3 , 5 , 6 , 7 }
如果要找的区间内对应的所有 a 3 [ i ] a_3[i] a 3 [ i ] 中有大于 L L L 而且小于 R R R 的数,那就成功了.输出 y e s . yes. y e s . 否则就是 n o no n o .
这个找符合规定的数的过程可以使用RMQ.记录每个区间内的最小数然后方便之后查找,并且具有最优性.(不信可以试试不同的数据)
2856-异或
1.如果 x ⊕ y = T x⊕y=T x ⊕ y = T ,那么一定满足 x ⊕ T = y x⊕T=y x ⊕ T = y ;
2.对于每个数 a i a_i a i ,如果能够存储在 a i a_i a i 的左侧,最后一个 x ⊕ T x⊕T x ⊕ T 的位置 p p p ,那么 [ L , R ] [L,R] [ L , R ] 中是否存在数对异或后为 T T T 的条件为:如果 [ L , R ] [L,R] [ L , R ] 中存在的一个 p p p ,满足 p ≥ L p\ge L p ≥ L 。
3.因此我们只要求出 [ L , R ] [L,R] [ L , R ] 中最大的 p p p ,如果 p ≥ L p\ge L p ≥ L ,即可满足条件,因此用RMQ,维护 [ L , R ] [L,R] [ L , R ] 区间 p p p 的最大值。
举例:
a [ ] = 2 , 1 , 3 , 4 , 2 , 3 , 2 , 3 a[]=2,1,3,4,2,3,2,3
a [ ] = 2 , 1 , 3 , 4 , 2 , 3 , 2 , 3
p [ ] = 0 , 0 , 1 , 0 , 3 , 5 , 6 , 7 p[]=0,0,1,0,3,5,6,7
p [ ] = 0 , 0 , 1 , 0 , 3 , 5 , 6 , 7
l a s t i last_i l a s t i :记录任何一个数的位置
如何求 p i p_i p i ?
1.用 t t t 数组来存储每个数的位置,每遇到一个数 a i a_i a i ,其位置 l a s t a i = i last_{a_i}=i l a s t a i = i ,显然如果有相同的 a i a_i a i ,l a s t a i last_{a_i} l a s t a i 将会记录最后一个 a i a_i a i 的位置;
2.因此查 a i ⊕ T a_i⊕T a i ⊕ T 的最后一个位置,直接取 l a s t a i ⊕ T last_{a_{i⊕T}} l a s t a i ⊕ T ,如果该位置不存在,则输出 n o no n o 。注意:由于 a i ⊕ T a_i⊕T a i ⊕ T 的最大值可能是 2 20 − 1 2^{20}-1 2 2 0 − 1 ,因此要注意 l a s t last l a s t 数组要开大一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 20 , L = 20 ;int f[N][L], p[N], ls[(1 << 20 ) + 10 ], lg[N];int n, m, l, r, t;int main () { scanf ("%d%d%d" , &n, &m, &t); for (int i = 1 ; i <= n; i++) { int x; scanf ("%d" , &x); p[i] = ls[x ^ t]; ls[x] = i; } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) lg[i] = lg[i >> 1 ] + 1 ; for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) f[j][i] = p[j]; else f[j][i] = max (f[j][i - 1 ], f[j + (1 << i - 1 )][i - 1 ]); } } while (m--) { scanf ("%d%d" , &l, &r); int len = lg[r - l + 1 ]; int maxx = max (f[l][len], f[r - (1 << len) + 1 ][len]); if (maxx >= l) puts ("yes" ); else puts ("no" ); } }
D.连续k个数的最值
题目描述
给定 n n n 个整数,求从第 1 1 1 个数到第 n − k + 1 n-k+1 n − k + 1 个数为起点的每个数开始,连续 k k k 个数的最大数和最小数。
0 ≤ k ≤ n ≤ 1 0 5 0\le k \le n \le 10^5 0 ≤ k ≤ n ≤ 1 0 5
输入
输出
解法
很好的板子题.跑两遍RMQ,分别记录最大最小值即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 , L = 20 ;int f[N][L], a[N], lg[N]; int fmi[N][L];int n, q;int main () { cin >> n >> q; for (int i = 1 ; i <= n; i++) { cin >> a[i]; } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) { lg[i] = lg[i / 2 ] + 1 ; } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) f[j][i] = a[j]; else f[j][i] = max (f[j][i - 1 ], f[j + (1 << (i - 1 ))][i - 1 ]); } } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) fmi[j][i] = a[j]; else fmi[j][i] = min (fmi[j][i - 1 ], fmi[j + (1 << (i - 1 ))][i - 1 ]); } } for (int i = 1 ; i + q - 1 <= n; i++) { int len = lg[q]; int minn = min (fmi[i][len], fmi[i + q - 1 - (1 << len) + 1 ][len]); int maxx = max (f[i][len], f[i + q - 1 - (1 << len) + 1 ][len]); cout << maxx << " " << minn << "\n" ; } return 0 ; }
E. 体育课
题目描述
体育课上,N N N 名同学排成了一排,他们的编号为 1 ∼ N 1 \sim N 1 ∼ N 。
体育老师安排大家玩 M M M 轮游戏,每轮游戏会从邀请编号在 [ L , R ] [L,R] [ L , R ] 之间的的同学参加。
这个游戏主要考验同学们的团队协作能力,不过,如果被邀请的同学身高差距太大,会很难完成游戏。
为了让每次邀请的同学都能顺利完成游戏,体育老师要求每次选取出编号在 [ L , R ] [L,R] [ L , R ] 之间的同学之后,请该组的同学告诉老师,这组同学最高身高和最低身高的差值是多少。老师将根据这个身高的差值,来设置游戏的难度。
输入
第 1 1 1 行读入 2 2 2 个整数 N , M N,M N , M 。
接下来 N N N 行每行读入一个整数,第 i i i 个整数 A i A_i A i 代表编号为 i i i 同学的身高。
接下来读入 M M M 行,每行读入 2 2 2 个整数 L , R L,R L , R ,表示被邀请参加游戏同学的编号范围。
输入
1 2 3 4 5 6 7 8 9 10 6 3 1 7 3 4 2 5 1 5 4 6 2 2
输出
数据范围
1 ≤ N ≤ 5 × 1 0 4 1 \le N \le 5\times10^4 1 ≤ N ≤ 5 × 1 0 4 ,1 ≤ M ≤ 2 × 1 0 5 1 \le M \le 2 \times 10^5 1 ≤ M ≤ 2 × 1 0 5 ,1 ≤ A i ≤ 1 0 6 1 \le A_i \le 10^6 1 ≤ A i ≤ 1 0 6 ,1 ≤ L ≤ R ≤ N 1 \le L \le R \le N 1 ≤ L ≤ R ≤ N 。
解法
跟Extra T1 一摸一样.数据范围反倒小啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;const int N = 1e4 * 5 + 10 , L = 20 ;int f[N][L], a[N], lg[N]; int fmi[N][L];int n, q, l, r;int main () { scanf ("%d%d" , &n, &q); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) { lg[i] = lg[i / 2 ] + 1 ; } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) f[j][i] = a[j]; else f[j][i] = max (f[j][i - 1 ], f[j + (1 << (i - 1 ))][i - 1 ]); } } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) fmi[j][i] = a[j]; else fmi[j][i] = min (fmi[j][i - 1 ], fmi[j + (1 << (i - 1 ))][i - 1 ]); } } while (q--) { scanf ("%d%d" , &l, &r); int len = lg[r - l + 1 ]; int maxx = max (f[l][len], f[r - (1 << len) + 1 ][len]); int minn = min (fmi[l][len], fmi[r - (1 << len) + 1 ][len]); printf ("%d\n" , maxx - minn); } return 0 ; }
没什么可说的,两次RMQ板子分别最大最小,还需要多打打板子题啊…好多小细节需要注意,但是大体上没什么难度.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;const int N = 1e5 * 5 + 10 , L = 20 ;int f[N][L], a[N], lg[N]; int fmi[N][L];int n, q, l, r;int main () { cin >> n >> q; for (int i = 1 ; i <= n; i++) { cin >> a[i]; } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) { lg[i] = lg[i / 2 ] + 1 ; } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) f[j][i] = a[j]; else f[j][i] = max (f[j][i - 1 ], f[j + (1 << (i - 1 ))][i - 1 ]); } } for (int i = 0 ; i < L; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { if (i == 0 ) fmi[j][i] = a[j]; else fmi[j][i] = min (fmi[j][i - 1 ], fmi[j + (1 << (i - 1 ))][i - 1 ]); } } while (q--) { cin >> l >> r; int len = lg[r - l + 1 ]; int maxx = max (f[l][len], f[r - (1 << len) + 1 ][len]); int minn = min (fmi[l][len], fmi[r - (1 << len) + 1 ][len]); cout << maxx - minn << "\n" ; } return 0 ; }
解法
就是模版好吧,不会的请看A题,注意:时间限制0.8s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 , L = 20 ;int a[N], f[N][L];int lg[N];int n, m, x, y, k;int main () { scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); } lg[1 ] = 0 ; for (int i = 2 ; i <= n; i++) lg[i] = lg[i / 2 ] + 1 ; for (int j = 0 ; j < L; j++) { for (int i = 1 ; i + (1 << j) - 1 <= n; i++) { if (j == 0 ) f[i][j] = a[i]; else f[i][j] = max (f[i][j - 1 ], f[i + (1 << j - 1 )][j - 1 ]); } } while (m--) { scanf ("%d%d" , &x, &y); k = lg[y - x + 1 ]; printf ("%d\n" , max (f[x][k], f[y - (1 << k) + 1 ][k])); } }