学习算法的时候,切忌心浮气躁,就像十年前的我,为了竞赛取得成绩,恨不得把所有算法都啃了,结果大部分算法最后都没有吃透,只是学了个皮毛,真正遇到问题的时候,脑子没有转过弯来,就很难把算法和实际问题的关系联系起来。
原因还是因为学的时候太过功利,只是为了眼前的成败得失,而忽略了更加重要的东西。
更加重要的东西并不是这个算法本身,而是设计算法的思维以及思考的过程,算法是死的,思维是活的!今天要介绍的这个算法,利用了动态规划的思想,可谓经典中的经典,希望读者在看完我的这篇文章后,也能够和我一样引起共鸣,为前人的思考点赞!
那么,让我们开始吧!为了共同的愿景而努力!让天下没有难学的算法!
int QueryMinIndex(int l, int r) {
int idx = l;
for(int i = l + 1; i <= r; ++i) {
if(A[i] < A[idx]) {
idx = i;
}
}
return idx;
}
ST(Sparse Table)算法是基于动态规划的,之前在说到动态规划的时候,有个很重要的概念就是状态。
求解区间最大值可以通过所有数字增加一个负号转化成求区间最小值问题,所以这里我们可以只讨论区间最小值问题。
这个算法也利用到了状态的概念,用 f [ i ] [ j ] f[i][j] f[i][j] 表示起点为 j j j,长度为 2 i 2^i 2i 的区间内的最大值所在下标。通俗的讲, f [ i ] [ j ] f[i][j] f[i][j] 就是区间 [ j , j + 2 i ) [j, j + 2^i) [j,j+2i) 内的最大值的下标(注意:这里表示的区间为左闭右开)。
如图二-3-1所示,对于长度为 16 的数组, f [ i ] [ j ] f[i][j] f[i][j] 所表示的区间如下:
由于区间 [ j , j + 2 i ) [j, j + 2^i) [j,j+2i) 长度为 2 i 2^i 2i,如果 i > 0 i > 0 i>0,那么它可以分解成两个长度为 2 i − 1 2^{i-1} 2i−1 的区间,即:
[ j , j + 2 i ) = [ j , j + 2 i − 1 ) + [ j + 2 i − 1 , j + 2 i ) [j, j + 2^i) = [j, j + 2^{i-1}) + [j + 2^{i-1}, j + 2^i) [j,j+2i)=[j,j+2i−1)+[j+2i−1,j+2i)
当 i = 3 , j = 4 i=3, j = 4 i=3,j=4 时,有 [ 4 , 12 ) = [ 4 , 8 ) + [ 8 , 12 ) [4, 12) = [4,8) + [8,12) [4,12)=[4,8)+[8,12),更加直观的,如图二-3-2所示:
由 图二-3-2 可知,父区间的最小值一定来自两个子区间中最小值的小者,对于数组 A A A,我们提供一个函数 RMQ_MinIndex(A[], l, r)
,函数传入两个下标,返回的是 A A A 数组中值较小的那个数的下标;
C++ 代码实现如下:
int RMQ_MinIndex(ValueType A[], int l, int r) {
return A[r] < A[l] ? r : l;
}
RMQ_MinIndex
函数;const int MAXM = 18;
const int MAXN = (1<<MAXM)+1;
void RMQ_Init(ValueType A[], int ALen, int(*f)[MAXN]) {
for (int i = 0; i < MAXM; i++) {
for (int j = 1; j + (1 << i) - 1 <= ALen; j++) {
if (i == 0) {
f[i][j] = j;
}
else {
f[i][j] = RMQ_MinIndex(A, f[i - 1][j], f[i - 1][j + (1 << (i - 1))]);
}
}
}
}
for (i = 1; i <= n; i++) {
lg2K[i] = k - 1;
if ((1 << k) == i) k++;
}
int k = lg2K[b - a + 1];
int RMQ_Query(ValueType A[], int(*f)[MAXN], int a, int b) {
if (a == b) {
return a;
}
else if (a > b) {
a = a^b, b = a^b, a = a^b; // 交换 a 和 b 的值
}
int k = lg2K[b - a + 1];
return RMQ_MinIndex(A, f[k][a], f[k][b - (1 << k) + 1]);
}
const int MAXM = 9;
const int MAXN = 301;
int RMQ_Pack(int r, int c) {
return (r << MAXM | c);
}
void RMQ_Init(ValueType A[MAXN][MAXN], int XLen, int YLen, int f[MAXM][MAXM][MAXN][MAXN]) {
int i, j, x, y, k;
for (i = 0; i < MAXM; i++) {
for (j = 0; j < MAXM; ++j) {
for (x = 1; x + (1 << i) - 1 <= XLen; ++x) {
for (y = 1; y + (1 << j) - 1 <= YLen; ++y) {
int &rf = f[i][j][x][y];
if (i == 0 && j == 0) {
rf = RMQ_Pack(x, y); // 1
}
else if (i == 0) {
rf = RMQ_MinIndex(A, f[i][j - 1][x][y], f[i][j - 1][x][y + (1 << (j - 1))]); // 2
}
else if (j == 0) {
rf = RMQ_MinIndex(A, f[i - 1][j][x][y], f[i - 1][j][x + (1 << (i - 1))][y]); // 3
}
else {
int a = RMQ_MinIndex(A,
f[i - 1][j - 1][x][y], // 4
f[i - 1][j - 1][x + (1 << (i - 1))][y + (1 << (j - 1))]); // 5
int b = RMQ_MinIndex(A,
f[i - 1][j - 1][x][y + (1 << (j - 1))], // 6
f[i - 1][j - 1][x + (1 << (i - 1))][y]); // 7
rf = RMQ_MinIndex(A, a, b);
}
}
}
}
}
}
RMQ_Pack
(左移+位或效率优于乘法)。i==0
的情况代表矩形X
维长度为1,所以退化成一维的情况,看代码的时候可以忽略 [i]
和 [x]
来看;j==0
的情况代表矩形Y
维长度为1,同样退化成一维的情况,看代码的时候可以忽略 [j]
和 [y]
来看;【例题1】给定一个长度为 n ( n < = 100000 ) n (n <= 100000) n(n<=100000) 的单调不降序列 a [ i ] a[i] a[i],然后是 q ( q < = 100000 ) q(q <= 100000) q(q<=100000) 条询问,问给定区间 [ l , r ] [l, r] [l,r] 出现最多的数的次数。
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
a[i] | -1 | -1 | 1 | 1 | 1 | 1 | 3 | 10 | 10 | 10 |
h[ a[i] ] | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 4 | 4 | 4 |
i | 1 | 2 | 3 | 4 |
---|---|---|---|---|
c[i] | 2 | 4 | 1 | 3 |
【例题2】小明有 n ( n < = 10000 ) n (n<=10000) n(n<=10000) 个宾馆的列表,每个宾馆有价格 p [ i ] p[i] p[i] 和 距离 d [ i ] d[i] d[i],对于宾馆 i i i,如果能够找到一个宾馆 j j j 同时满足 ( p [ j ] < p [ i ] 且 d [ j ] < d [ i ] ) (p[j] < p[i] 且 d[j] < d[i]) (p[j]<p[i]且d[j]<d[i]),那么 i i i 这家宾馆他就不会考虑,请给出小明的候选宾馆列表。
把 p [ i ] p[i] p[i] 和 d [ i ] d[i] d[i] 看成是二维平面上的点。如图三-2-1所示,红色的为候选列表,灰色的为淘汰列表。
比较直观的感觉就是,在 原点 和 当前点 组成的矩形中,如果没有其他点,那么它就能够作为候选列表。
那么,我们可以把宾馆数据按照 p [ i ] p[i] p[i] 递增排序,如果 p [ i ] p[i] p[i] 相等,则按照 d [ i ] d[i] d[i] 递减排序,对于某个 i i i ,如果所有的 j < i j < i j<i 都满足 d [ j ] > = d [ i ] d[j] >= d[i] d[j]>=d[i],则 i i i 就是一个候选宾馆。
再明确一点,规则可以转化成:如果所有的 j < i j < i j<i 中的最小值 d [ j ′ ] > = d [ i ] d[j'] >= d[i] d[j′]>=d[i],那么 i i i 就是一个候选宾馆。
于是,对 d [ j ] d[j] d[j] 数组预处理 R M Q RMQ RMQ 即可。
【例题3】给定 n ( n < = 1 0 5 ) n(n <= 10^5) n(n<=105) 个数 a [ i ] a[i] a[i], Q ( Q < = 1 0 5 ) Q(Q<=10^5) Q(Q<=105) 次询问 [ l , r ] [l, r] [l,r] 区间内所有数的 g c d gcd gcd (最大公约数)。
在学习初等数论的时候,我们已经了解到 g c d gcd gcd 的定义如下:
g c d ( a , b ) = p 1 m i n ( x 1 , y 1 ) p 2 m i n ( x 2 , y 2 ) p 3 m i n ( x 3 , y 3 ) . . . p k m i n ( x k , y k ) gcd(a,b)=p_1^{min(x_1,y_1)}p_2^{min(x_2,y_2)}p_3^{min(x_3,y_3)}...p_k^{min(x_k,y_k)} gcd(a,b)=p1min(x1,y1)p2min(x2,y2)p3min(x3,y3)...pkmin(xk,yk)
可以把问题转换成指数的最值问题, R M Q RMQ RMQ 求解;
状态转移方程如下:
f [ i ] [ j ] = { a [ j ] i = 0 g c d ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j + 2 i − 1 ] ) i > 0 f[i][j] = \begin{cases} a[j] & i=0\\ gcd(f[i-1][j], f[i-1][j + 2^{i-1}]) & i>0 \end{cases} f[i][j]={ a[j]gcd(f[i−1][j],f[i−1][j+2i−1])i=0i>0
这里 f [ i ] [ j ] f[i][j] f[i][j] 不再存下标,而是存了实际的 g c d gcd gcd 值;
查询的话,还是参考区间最小值的找最小重叠区间的方案;
【例题4】 n ( n < = 50000 ) n(n <= 50000) n(n<=50000) 个长度不同的数字 s [ i ] s[i] s[i] 排成一排,求最大的 j − i j-i j−i,对所有的 s [ k ] ( i < k < j ) s[k](i < k < j) s[k](i<k<j),都满足 ( a [ i ] < a [ k ] < s [ j ] ) (a[i] < a[k] < s[j]) (a[i]<a[k]<s[j])。
s[ RMQMin(i+1, t) ] > s[i]
,则表示 t t t 还可以往大扩,找到一个最大的 t t t 以后,再用 j = RMQMax(i+1, t);
来获取 j j j,再用 j − i j-i j−i 来更新最大值,算法时间复杂度 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))。【例题5】给定 n n n 个数字 a [ i ] a[i] a[i],以及 m m m 次询问 [ l , r ] [l, r] [l,r] ( 1 ≤ n , m ≤ 200000 , 1 < = l < = r < = n ) (1 ≤ n, m ≤ 200000, 1<=l<=r<=n) (1≤n,m≤200000,1<=l<=r<=n),询问区间内的最长连续不重复子串的长度。例如 9 个数字的序列如下:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
a[i] | 2 | 5 | 4 | 1 | 2 | 3 | 6 | 2 | 4 |
区间 [ 1 , 6 ] [1,6] [1,6] 的最长连续不重复子串为 [ 2 , 6 ] = ( 5 , 4 , 1 , 2 , 3 ) [2,6]=(5,4,1,2,3) [2,6]=(5,4,1,2,3),长度为 5;
区间 [ 5 , 9 ] [5,9] [5,9] 的最长连续不重复子串为 [ 6 , 9 ] = ( 3 , 6 , 2 , 4 ) [6,9]=(3,6,2,4) [6,9]=(3,6,2,4),长度为 4;
区间 [ 5 , 8 ] [5,8] [5,8] 的最长连续不重复子串为 [ 5 , 7 ] = ( 2 , 3 , 6 ) [5,7]=(2,3,6) [5,7]=(2,3,6) 或 [ 6 , 8 ] = ( 3 , 6 , 2 ) [6,8]=(3,6,2) [6,8]=(3,6,2),长度为 3;
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
pre[i] | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 5 | 3 |
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
dp[i] | 1 | 2 | 3 | 4 | 4 | 5 | 6 | 3 | 4 |
那么,当我们进行区间询问 [ l , r ] [l, r] [l,r] 时,对应所有的 i i i 满足 l < = i < = r l <= i <= r l<=i<=r,以 i i i 结尾的子串中,最长不重复子串的长度 x [ i ] x[i] x[i] 满足:
x [ i ] = { d p [ i ] i − d p [ i ] + 1 > = l i − l + 1 i − d p [ i ] + 1 < l x[i] = \begin{cases} dp[i] & i-dp[i]+1 >= l\\ i-l+1 & i-dp[i]+1 < l \end{cases} x[i]={ dp[i]i−l+1i−dp[i]+1>=li−dp[i]+1<l
于是,我们再生成后一个辅助数组: i d p [ i ] = i − d p [ i ] + 1 idp[i] = i - dp[i] +1 idp[i]=i−dp[i]+1
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
idp[i] | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 6 | 6 |
题目链接 | 难度 | 解法 |
---|---|---|
Balanced Lineup | ★☆☆☆☆ | RMQ 模板题 |
HDU 1806 Frequent values | ★☆☆☆☆ | 离散化 + RMQ |
HDU 6098 Inversion | ★★☆☆☆ | 分段统计 + RMQ |
HDU 3183 A Magic Lamp | ★★☆☆☆ | 鸽巢原理 |
HDU 5289 Assignment | ★★☆☆☆ | 二分 + RMQ / 单调队列 |
HDU 3530 Subsequence | ★★☆☆☆ | 二分 + RMQ / 单调队列 |
PKU 2452 Sticks Problem | ★★☆☆☆ | 二分+RMQ |
HDU 3193 Find the hotel | ★★☆☆☆ | 逆序数 |
HDU 6406 Taotao Picks Apples | ★★★☆☆ | 二分 + 动态规划 + RMQ |
HDU 5247 找连续数 | ★★★☆☆ | HASH + RMQ |
HDU 5172 GTY’s gay friends | ★★★☆☆ | RMQ, 卡内存过不了,线段树求解 |
HDU 3486 Interviewe | ★★★☆☆ | 枚举 + RMQ |
HDU 4252 A Famous City | ★★★☆☆ | 离散化 + RMQ |
PKU 2201 Cartesian Tree | ★★★☆☆ | 构造笛卡尔树 |
PKU 2019 Cornfields | ★★★☆☆ | 二维RMQ |
ZJU 2859 Matrix Searching | ★★★☆☆ | 二维RMQ |
HDU 2888 Check Corners | ★★★☆☆ | 二维RMQ |
HDU 2305 WorstWeather Ever | ★★★★☆ | 二分+RMQ |
PKU 3419 Difference Is Beautiful | ★★★★☆ | 动态规划 + RMQ |
HDU 4123 Bob’s Race | ★★★★☆ | 树形DP + RMQ / 单调队列 |
HDU 5696 区间的价值 | ★★★★☆ | 单调队列 + RMQ |
HDU 5726 GCD | ★★★★☆ | RMQ 的 GCD 变种 |
HDU 5371 Hotaru’s problem | ★★★★☆ | Manacher + RMQ |
HDU 2459 Maximum repetition substring | ★★★★☆ | 后缀数组 + RMQ |
HDU 3948 The Number of Palindromes | ★★★★☆ | 后缀数组 + RMQ |
HDU 5558 Alice’s Classified Message | ★★★★☆ | 后缀数组 + RMQ |