您的位置:首页 > 其它

BZOJ 1699 & 1636 POJ 3264: [Usaco2007 Jan]Balanced Lineup排队 ST算法简介

2012-07-24 12:57 369 查看
这个题目同时是BZOJ 1699 BZOJ 1636 和POJ 3264的题目……

是最最经典的RMQ(区间最值问题)

我为了复习ST刻意下了一下……(3W 汗出翔)

分别数组开小了, log(a)/log(b) 的double问题, 还有初始化的边界…… 3W压力大

还好复习了一下……

min_num[i][j] 表示从a[i]这个数字开始,到a[i + 2^j -1]个数字的最小值

那么显然 min_num[i][0] = a[0] 因为他是a[i]到 a[i + 2^0 - 1]也就是a[i + 0]的最小值

那么显然 min_num[i][k] = min( min_num[i][k - 1] , min_num[i + 2^(k-1) ][k - 1]);

再举例子~ 如果k = 2的情况下

min_num[i][k] 也就是i 到 (i + 2^2 - 1)=(i + 3)的位置的最小值,换句话说,k就是长度为2^k一段数字的最小值。 2^k一段的最小值,可以由2段2^(k-1)得到

那么i到i+3的最小值,可以由[i,i+1] 和 [i+2, i+3]合并得到

(实现的时候2^p, 显然就是1<<p 了)

int m = i + (1 << (j - 1));
min_num[i][j] = min(min_num[i][j - 1], min_num[m][j - 1]);
显然这一段代码就可以实现min_num的取值

但是我们在外层循环穷举i,j的时候,到底j最大取什么呢.

显然[L,R]所表示的区间最小值,他的R不能n,L也不能小于1 (这里默认n为数字的个数了~)

min_num[i][j]表示的是i,到i+2^j-1

i最小当然是1啦~ 也就是说1+2^j-1 <=n

也就是~ 2^j <=n

那么我们可以

int k = (int)(log((double)n)/log(2.0));


求出j的最大上限

那么显然 i+2^j-1 <= n,在循环i的时候只要满足这个条件i就可以一直循环下去~

for (int i = 1; i + (1<<j)-1 <=n; ++ i)
也就是这样~

这个就是初始化的完整code了~ (我这里同时还求了max_num,大同小异啦)

for (int i = 1; i <= n; ++ i)	min_num[i][0] = max_num[i][0] = a[i];
int k = (int)(log((double)n)/log(2.0));
for (int j = 1; j <= k; ++ j)
{
for (int i = 1; i + (1<<j)-1 <=n; ++ i)
{
int m = i + (1 << (j - 1));
min_num[i][j] = min(min_num[i][j - 1], min_num[m][j - 1]);
max_num[i][j] = max(max_num[i][j - 1], max_num[m][j - 1]);
}
}


我们得到了初始化数组下面有什么用呢……如何快速求出区间最值呢……

我们考虑一个问题,比如求[3,8]的区间最值,我们可以改一下

改为求[3,6] 和[5,8]的区间最值的最小值~ 答案是不变的。

但是我们如何拆分这个区间比较合适呢……

我们试图把[L,R]他拆为a[i1][j] a[i2][j]

[i1, i1+ 2^j - 1] [i2, i2+2^j -1]

其中

i1 = L

i2 + 2 ^ j - 1 = R

i1 + 2^j - 1 >= i2

显然i1+ 2^j - 1 > mid(L,R)

i2 < mid(L,R)

也就是说

i1 + 2^(j+1) - 1 > R 因为j本身应该>=区间的一半,j再+1那就一定超过区间了

同理,j-1那么一定不到区间的一半

i1 + 2^(j - 1) - 1 < mid(L,R)

区间的的长度是R-L+1

那么j就是在 2^j <= R-L+1 当中j最大的数字

所以j = log(R-L+1)/log(2)

那么因为重新划分的区间是[i1, i1+ 2^j - 1] [i2, i2+2^j -1]

i2+2^j-1 = R

所以i2 = R-2^j+1

i1,i2,j都有了,

那么L,R区间的最值一定就是min (min_num[i1][j], min_num[i2][j]);

inline int ask_min(int x, int y)//x,y区间的最值
{
int k = log((double)(y - x + 1))/log(2.0);
return min(min_num[x][k], min_num[y - (1<<k) + 1][k]);
}


于是乎,整个问题都完美的解决了。

构造的时间复杂度是O(nlogn) ,查询的时间复杂度是O(1)

相比较线段树来说,ST算法有着更高的效率,更小的代码量~

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;

const int max_n = 50000 + 10;
int n, x, y, t;
int a[max_n] = {0};
int min_num[max_n][17] = {0}, max_num[max_n][17] = {0};

inline int ask_min()//x,y区间的最值
{
int k = log((double)(y - x + 1))/log(2.0);
return min(min_num[x][k], min_num[y - (1<<k) + 1][k]);
}

inline int ask_max()
{
int k = log((double)(y - x + 1))/log(2.0);
return max(max_num[x][k], max_num[y - (1<<k) + 1][k]);
}

inline void read(int &x)
{
char ch;
while (ch=getchar(),ch>'9' || ch<'0') ;
x=ch-48;
while (ch=getchar(),ch<='9' && ch>='0') x=x*10+ch-48;
}

int main()
{
read(n); read(t);
for (int i = 1; i <= n; ++ i)	scanf("%d", &a[i]);
for (int i = 1; i <= n; ++ i)	min_num[i][0] = max_num[i][0] = a[i];
int k = (int)(log((double)n)/log(2.0));
for (int j = 1; j <= k; ++ j)
{
for (int i = 1; i + (1<<j)-1 <=n; ++ i)
{
int m = i + (1 << (j - 1));
min_num[i][j] = min(min_num[i][j - 1], min_num[m][j - 1]);
max_num[i][j] = max(max_num[i][j - 1], max_num[m][j - 1]);
}
}
while (t --)
{
read(x); read(y);
printf("%d\n", ask_max()-ask_min());
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: