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 了)
但是我们在外层循环穷举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
那么我们可以
求出j的最大上限
那么显然 i+2^j-1 <= n,在循环i的时候只要满足这个条件i就可以一直循环下去~
这个就是初始化的完整code了~ (我这里同时还求了max_num,大同小异啦)
我们得到了初始化数组下面有什么用呢……如何快速求出区间最值呢……
我们考虑一个问题,比如求[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]);
于是乎,整个问题都完美的解决了。
构造的时间复杂度是O(nlogn) ,查询的时间复杂度是O(1)
相比较线段树来说,ST算法有着更高的效率,更小的代码量~
是最最经典的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; }
相关文章推荐
- bzoj 1636 && bzoj 1699: [Usaco2007 Jan]Balanced Lineup排队(RMQ)
- bzoj:1699;poj 3264: [Usaco2007 Jan]Balanced Lineup排队
- bzoj 1699: [Usaco2007 Jan]Balanced Lineup排队 分块
- BZOJ 1699: [Usaco2007 Jan]Balanced Lineup排队( RMQ )
- 【bzoj 1699 & 1636】【POJ 3264】Balanced Lineup(st表|RMQ)
- 【BZOJ】1699: [Usaco2007 Jan]Balanced Lineup排队(rmq/树状数组)
- 【BZOJ】1699 [Usaco2007 Jan]Balanced Lineup排队 ST表
- 【bzoj1699】[Usaco2007 Jan]Balanced Lineup排队
- BZOJ1699: [Usaco2007 Jan]Balanced Lineup排队
- BZOJ 1699 [Usaco2007 Jan]Balanced Lineup排队 线段树
- 【bzoj 1699】[Usaco2007 Jan]Balanced Lineup排队
- bzoj 1699: [Usaco2007 Jan]Balanced Lineup排队【st表||线段树】
- 【bzoj1699】[Usaco2007 Jan][Balanced Lineup排队]
- BZOJ 1699: [Usaco2007 Jan]Balanced Lineup排队
- BZOJ 1699 [USACO2007 Jan] Balanced Lineup排队
- 1636/1699: [Usaco2007 Jan]Balanced Lineup排队
- bzoj1699 [Usaco2007 Jan]Balanced Lineup排队
- BZOJ 1699 [Usaco2007 Jan]Balanced Lineup排队 线段树
- BZOJ 1636: [Usaco2007 Jan]Balanced Lineup
- 【BZOJ】1636: [Usaco2007 Jan]Balanced Lineup(rmq+树状数组)