【解题报告】NYOJ 119 士兵杀敌(三)--RMQ算法详解

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

 

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

 

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样式已经失去了,有些效果看不出来,所以提供一个  -> 文档下载地址 :早已上传于百度文库  欢迎下载



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值