POJ 3264 Balanced Lineup RMQ问题 ST算法 O(1)查找区间最值

来源:互联网 发布:修复痘印 知乎 编辑:程序博客网 时间:2024/04/29 21:36

原题: http://poj.org/problem?id=3264

题目:

Balanced Lineup
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 40235 Accepted: 18895
Case Time Limit: 2000MS
Description

For the daily milking, Farmer John’s N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.

Farmer John has made a list of Q (1 ≤ Q ≤ 200,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.

Input

Line 1: Two space-separated integers, N and Q.
Lines 2..N+1: Line i+1 contains a single integer that is the height of cow i
Lines N+2..N+Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.
Output

Lines 1..Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.
Sample Input

6 3
1
7
3
4
2
5
1 5
4 6
2 2
Sample Output

6
3
0

思路:

给定n个数的值,求区间最大值和最小值的差。

线段树可以以nlogn的效率完成预处理,以logn的效率完成查找区间最值,这里介绍一种nlogn处理,O(1)查询的方法。就是st算法。

这里我们来看一个表。
这里写图片描述
第一排表示的是以该点为起点,连续的2^0个数的最大值。
第二排表示的是以该点为起点,连续的2^1个数的最大值。
第三排表示的是以该点为起点,连续的2^2个数的最大值。
……
第n排表示的是以该点为起点,连续的2^2个数的最大值。

因为这个我们每次的数据范围扩大2,所以能由上面一层的两个最大值取大得到区间的最大值。

假设我们现在有了这么一张表。

对于任意数区间ab,我们假设存在x满足a <= x <= b,并使得x=a-1+2^k,那么我们可以把区间分成两部分(注意,这两部分可以有交集):
[a,a+2^k-1]和[b-2^k+1,b]。
显然通过上面的dp递推式,我们可以得到以x为起点,连续2^k个数的最大值。也就是a到a-1+2^k的最大值。
然后我们求剩下区间的最大值,我们可以找到以b-2^k+1为起点,连续2^k个数的最大值。

感觉是不是有点绕?
我们举个例子,假如a=1,b=2,显然区间要分为[1,1]和[2,2],这里的k=0。
假如a=3,b=9,那么k=2。分为[3,6]和[6,9]。
假如a=3,b=13,那么k=3。分为[3,10]和[6,13]。
因为上面两个部分的最大值我们总是已经求出来了的,所以只需要比较一次就可以得出答案,时间复杂度是O(1)。

我们像上面例表一样先建一个dp数组供查询。
建表的核心代码如下:

for(i=1;i<=log((double)n)/log(2.0);i++)        for(j=1;j+(1<<i)-1<=n;j++)        {            dpmax[i][j]=max(dpmax[i-1][j],dpmax[i-1][j+(1<<(i-1))]);        }

log((double)n)/log(2.0);这里是对数的换底公式,意思是求log2(n)向下取整。
1<<i这里是移位操作,等价于1*2^i,因为我们需要每层的dp值包含的数更多。

而取最值的算法如下:

int getmax(int a,int b){    int k=(int)(log((double)(b-a+1))/log(2.0));    return max(dpmax[k][a],dpmax[k][b-(1<<k)+1]);}

这里用到的是上面的把区间分为两部分,求最值。

代码:

#include <iostream>#include"stdio.h"#include"math.h"using namespace std;const int N=50005;int a[N];int dpmax[40][N];int dpmin[40][N];void rmq(int n){    int i,j;    for(i=1;i<=n;i++)    {        dpmin[0][i]=a[i];        dpmax[0][i]=a[i];    }    for(i=1;i<=log((double)n)/log(2.0);i++)        for(j=1;j+(1<<i)-1<=n;j++)        {            dpmax[i][j]=max(dpmax[i-1][j],dpmax[i-1][j+(1<<(i-1))]);            dpmin[i][j]=min(dpmin[i-1][j],dpmin[i-1][j+(1<<(i-1))]);        }}int getmax(int a,int b){    int k=(int)(log((double)(b-a+1))/log(2.0));    return max(dpmax[k][a],dpmax[k][b-(1<<k)+1]);}int getmin(int a,int b){    int k=(int)(log((double)(b-a+1))/log(2.0));    return min(dpmin[k][a],dpmin[k][b-(1<<k)+1]);}int main(){    int n,m;    while(scanf("%d %d",&n,&m)!=EOF)    {        for(int i=1;i<=n;i++)        {            scanf("%d",&a[i]);        }        rmq(n);        for(int i=1;i<=m;i++)        {            int a,b;            scanf("%d %d",&a,&b);            printf("%d\n",getmax(a,b)-getmin(a,b));        }    }    return 0;}
0 0
原创粉丝点击