sparse table algorithm 简称ST算法,可以用于求解RMQ问题(range minimum query)
ST算法是一个在线算法,在线算法即对于每一个查询可以马上给出解答。但需要较长的时间来进行预处理。
在O(nlogn)的时间内进行预处理,而可以在O(1)的时间内解答每个询问。
预处理:
ST算法预处理利用的是dp的思想。
定义状态dp[i][j] 表示的是从第i个数起2^j个数中最小的数。
举一个例子:
2 9 1 4 5 3 4 5 6这个序列
dp[0][0] 为2 dp[0][1] = 2 dp[0][2] = 1
初始化这个dp数组,dp[i][0] = a[i] (
0<i<n
)
状态的转移:
可以发现dp[i][j]代表的数字一定是一个偶数(j = 0 除外),考虑将2^j的长度分成两份,长度都为2^(j-1)。
两端分别为 i ~ i + 2^(j-1)-1 , i + 2^(j-1) ~ i + 2^j - 1
沿着这思路转移方程:
dp[i][j] = min(dp[i][j-1],dp[i + 2^(j-1)][j-1]) ( 0<= , 2^j < n ,1 <= i < n - 2^j + 1)
查询:
RMQ(i,j) 查询 i ~ j 之间
区间长度len = j - i + 1。求出 2^k <= len < 2^(k+1)
k = log(len) / log(2) ( log 在c语言中同时以e为底)
RMQ(i,j) = min(dp[i][k] , dp[j - 2^k + 1][k])
(这里第二个参数要用dp[j - 2^k + 1][k] 不能用dp[i + 2^k - 1][k] ,因为后者可能是没有定义的)
例如
1 7 3 4 2 5
查询RMQ(0,4) len = 5,k = 2;
dp[0][2] = 1, dp[j - 2^k + 1][2] = dp[1][2] = 2 ,而dp[i + 2^k - 1][k] = dp[0 + 4 - 1][2] 这是没有定义的,因为 3 + 2^2 -1 = 6 >= n,这个在状态转移的时候是不会被更新的,因为这个数组本身没有这么长,这一段在中间的时候就已经到达数组结尾了。
poj 3264
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define M 50009
int a[M];
int mm[M][100];
int mi[M][100];
int n,q;
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d %d",&n,&q) == 2)
{
for(int i = 0;i < n;i++) scanf("%d",&a[i]);
for(int i = 0;i < n;i++)
{
mm[i][0] = a[i];
mi[i][0] = a[i];
}
for(int j = 1;(1<<j) <= n;j++)
{
for(int i = 0;i + (1<<j) - 1 < n;i++)
{
int m = i + (1<<(j-1));
mm[i][j] = max(mm[i][j-1],mm[m][j-1]); // i ~ i + 2^(j-1)-1 , i + 2^(j-1) ~ i + 2^j - 1
mi[i][j] = min(mi[i][j-1],mi[m][j-1]);
}
}
while(q--)
{
int a,b;
scanf("%d %d",&a,&b);
a--;
b--;
int len = b - a + 1;
int m = (int)(log((double)len) / log(2.0));
int maxx = max(mm[a][m],mm[b-(1<<m)+1][m]);
int minn = min(mi[a][m],mi[b-(1<<m)+1][m]);
printf("%d\n",maxx - minn);
}
}
return 0;
}