RMQ问题
题目连接:http://acm.nyist.net/JudgeOnline/problem.php?pid=119------ 士兵杀敌(三)
大意就是,有一个数列 {a1,a2,a3,a4,a5,a6,a7.......an} ,在其中连续的区间内,算出该区间内最大最小值的差。
示范数据:9 2 5 1 10 7 3 21 8 15 4 22 19 6
[ 注 ]:
以下max(a, b) 表示在a和b两个之间取最大值,比如max(a1, a4)表示a1 和a4两个中的最大值。
以下将Fmax[a][b] 数组简写为 Fmax(a, b)。
不过我们会在同一个数列中查询很多很多很多次,因此导致暴力的算法超时而失效。
因此我们引入基于DP思想的ST算法 (附上我参考的连接:http://wenku.baidu.com/view/6a7d691aa8114431b90dd877.html )
这个算法,将整个过程分成了两个,一个是数列的初始化,一个是区间的查询。
由于DP思想,我们在初始化中,需要i两个数组来保存任意一个区间的最大最小值。
但是如果我们将数组的下标Fmax(a,b)当做区间的端点[a,b]来处理的话,这样时间复杂度为 O(n2 ),也就是说你遍历完一个子区间之后,再把最值存在数组中,但是你必须遍历所有子区间,这显然又会超时。
于是我们想到遍历的时候采用分治思想,对数列进行归并。(一下均以求最大值为例)
首先,所开数组Fmax(a,b)表示 区间[a,b] 的最大值
第一次归并:
Fmax(1, 1) = a1; Fmax(2, 2) = a2; Fmax(3, 3) = a3; Fmax(4,4) = a4; …….
….
Fmax(n, n) = an
第二次归并:
Fmax(1, 2) = max(Fmax(1, 1), Fmax(2, 2)); Fmax(2, 3) = max(Fmax(2, 2), Fmax(3, 3));
Fmax(3, 4) = max(Fmax(3, 3), Fmax(4, 4)) ; ……
….
Fmax(n-1, n) = max(Fmax(n-1, n-1), Fmax(n, n))
第三次归并:(逐渐有一点二分法的味道了 )
Fmax(1, 3) = max(Fmax(1, 2), Fmax(2, 3)); Fmax(2, 4) = max(Fmax(2, 3), Fmax(3, 4));
Fmax(3, 5) = max(Fmax(3, 4), Fmax(4,5)) ; ……
….
Fmax(n-2, n) =max(Fmax(n-2, n-1), Fmax(n-1, n))
...............
...............
第 N 次归并之后:
直接上递推公式:Fmax(1, n) = max(Fmax(1, (1+n)/2), Fmax((1+n)/2, n);
也就是说,如果我们得到了这样一个存好了的Fmax (m, n) 数组的话,查询起来只需要一次比较:
Fmax(m, n) = max(Fmax(m, (m+n)/2), Fmax((m+n)/2, n)) 就能找到最大值了。
这个算法看上去很不错,但是仔细一想,士兵一共最多有十万个,也就是说区间最大有 [1, 100000],那么数组至少得开成这样大才行:Fmax[100000][ 100000] 内存占用:10000000000绝对超上限了!因此这个办法也不行。
那我们应该怎么办呢?
仔细想一想,我们又可以发现刚刚那个算法在分区间的时候,是没有遗漏没有重复的把所给定区间一分为二了,为什么我们不能分区间的时候有重复呢(如下图所示)?而且在归并的时候每一次把区间扩大了整整一倍。因此我们尝试将 Fmax二维数组下标的含义改变一下
var | 9 | 2 | 5 | 1 | 10 | 7 | 3 | 21 | 8 | 15 | 4 | 22 | 19 | 6 |
sub | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
我们规定Fmax(a, b) 表示 区间[a, a + 2b- 1]的最大值,即:从a开始往后的2b– 1个元素止,共2b个元素中的最大值
例如:Fmax(3,1) 表示 区间[3, 3 + 21-1] = [3, 4] 的最大值。
由规定可得:Fmax(1, 0) 表示[1, 1] ,即a1 。
所以:Fmax(3,0) = a3 Fmax(4, 0) = a4 Fmax(5, 0) = a5 ……..Fmax(m,0) = am
Fmax(1, 1) = max(Fmax(1, 1- 1), Fmax(1 + 21 - 1, 1- 1) ) //从1开始往后
Fmax(2, 1) = max(Fmax(2, 1- 1), Fmax(2 + 21 - 1, 1- 1) ) //从2开始往后
Fmax(3, 1) = max(Fmax(3, 1- 1), Fmax(3 + 21 - 1, 1- 1) ) //从3开始往后
.......
Fmax(m, 1) = max(Fmax(m, 1- 1), Fmax(m + 21 - 1, 1- 1) ) //到m结束了
---------------------------------------------------------------------------------------------------------------
Fmax(1, 2) = max(Fmax(1, 2- 1), Fmax(1 + 22 - 1, 2- 1) ) //从1开始往后
Fmax(2, 2) = max(Fmax(2, 2- 1), Fmax(2 + 22 - 1, 2- 1) ) //从1开始往后
........
Fmax(m, 2) = max(Fmax(m, 2- 1), Fmax(m + 22- 1, 2- 1) ) // 这个地方的m并不等于上面边界的m
---------------------------------------------------------------------------------------------------------------
归纳出动态转移方程式:
Fmax(m, n) = am(n==0 m=1,2,3,4...)
Fmax(m, n) = max(Fmax(m, n- 1), Fmax(m +2n-1, n- 1)) (n!=0 m=1,2,3,4...)
于是我们可以得到如下的表格(遍历储存的顺序应该是左边第一竖排开始往右,紫色部分是我一开始弄错了的不应该有的,NULL表示数组的这个位置应该是没有值的):
sub | value | sub | value | sub | value | sub | value |
0,0 | . | 0,1 | . | 0,2 | . | 0,3 | . |
1,0 | 9 | 1,1 | 9 | 1,2 | 9 | 1,3 | 21 |
2,0 | 2 | 2,1 | 5 | 2,2 | 10 | 2,3 | 21 |
3,0 | 5 | 3,1 | 5 | 3,2 | 10 | 3,3 | 21 |
4,0 | 1 | 4,1 | 10 | 4,2 | 10 | 4,3 | 21 |
5,0 | 10 | 5,1 | 10 | 5,2 | 21 | 5,3 | 22 |
6,0 | 7 | 6,1 | 7 | 6,2 | 21 | 6,3 | 22 |
7,0 | 3 | 7,1 | 21 | 7,2 | 21 | 7,3 | 22 |
8,0 | 21 | 8,1 | 21 | 8,2 | 21 | 8,3 | 22 |
9,0 | 8 | 9,1 | 15 | 9,2 | 22 | 9,3 | 22 |
10,0 | 15 | 10,1 | 15 | 10,2 | 22 | 10,3 | 22 |
11,0 | 4 | 11,1 | 22 | 11,2 | 22 | 11,3 | NULL |
12,0 | 22 | 12,1 | 22 | 12,2 | 22 | 12,3 | NULL |
13,0 | 19 | 13,1 | 19 | 13,2 | NULL | 13,3 |
|
14,0 | 6 | 14,1 | NULL | 14,2 |
| 14,3 |
|
然后既然知道了Fmax的含义,查询过程就容易理解了,已知所给区间为[a, b],我直接附上查询的公式:
令 k 为满足 2k <= b – a + 1 不等式的最大值
则:区间[a, b]最大值 = max(Fmax(a,k),Fmax(b- 2k+1, k))
例如:区间[2, 11],k=3, max(Fmax(2, 3), Fmax(11 – 23+1,3))=> max(Fmax(2, 3), Fmax(4, 3))
=>取 区间[2, 9]最大值和区间max[4, 11]最大值 的最大值 => answer: 22
9 | 2 | 5 | 1 | 10 | 7 | 3 | 21 | 8 | 15 | 4 | 22 | 19 | 6 | |
sub | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
AC 代码 :
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 100010 // soldiers
int maxmem[N][20]; // max 2^18=262144
int minmem[N][20]; // min
void Initialize(int n_soldier){
for (int i=1;i <= n_soldier; i++){
scanf("%d",&maxmem[i][0]);
minmem[i][0] = maxmem[i][0];
}
int m,n;
for (n = 1;(1<<(n-1)) <= n_soldier; n++){ //notice the limit
for (m = 1; m+(1<<n)-1 <= n_soldier; m++){ //notice the limit
maxmem[m][n] = max(maxmem[m][n-1],maxmem[m + (1 << (n-1))][n-1]);
minmem[m][n] = min(minmem[m][n-1],minmem[m + (1 << (n-1))][n-1]);
}
}
}
void Query(){
int left,right;
scanf("%d%d",&left,&right);
int k=floor(log(double(right-left+1.00))/log(2.0));
int maxv = max(maxmem[left][k],maxmem[right-(1<<k)+1][k]);
int minv = min(minmem[left][k],minmem[right-(1<<k)+1][k]);
printf("%d\n",maxv-minv);
}
int main()
{
int n_soldier,n_query;
scanf("%d %d",&n_soldier,&n_query);
Initialize(n_soldier); // Initialization
while(n_query--) // many many many times queries
Query();
return 0;
}
由于csdn上面的word样式已经失去了,有些效果看不出来,所以提供一个 -> 文档下载地址 :早已上传于百度文库 欢迎下载