简介
st表是解决RMQ问题(静态区间最值问题)的一种强有力的工具
它可以做到O(nlogn)预处理,O(1)查询最值
原因:倍增中的层数是log2(n),即2^j <= n, 所以d中元素不超过n*logn, 而每个元素都可以在常数的范围内计算完毕,所以总时间是O(nlogn)
算法
d(i,j)表示从 i 开始的,长度为2的 j 次方的一段元素中的最值,可用递推计算d
找 i 的二进制数中的第一位
d(i,j) = max/min(d(i, j-1), d(i+2^j, j-1) )
//以最大值为例 (虽然RMQ的意思是最小值
void st() {
for(int i = 1; i <= n; i++) f[i][0] = a[i];
for(int k = 1; (1<<k) <= n; k++)
for(int i = 1; i+(1<<k)-1 <= n; i++) { //有边界
d[i][k] = max(d[i][k-1], d[i + (1<<(k-1))][k-1] );
}
}
int RMQ(int l, int R) {
int k = 0;//使k为满足2^k <= R-L+1的最大整数,则以L开头, 以R结尾的两个长为2^k的区间合起来就覆盖了整个[L,R]
while(( 1<<(k+1) ) <= R-L+1) k++;//如果写"(1<<k) <= R-L+1" , k还会再++,这样就可能越界了
return max(d[L][k], d[R-(1<<k)+1][k]);
}
例题
tiyi:
给出一个非降序的整数数组,你的任务是对于一系列询问(i,j),回答区间内出现最多的值的次数
输入格式:
多组数据
每组: 第一行n,q(访问数)(1<=n,q<=100000) 。第二行n个非降序的整数(-100000<=a<=100000).以下q行,包括 i,j(合法的),输入结束标志为n==0
分析
因为整个数组是非降序的,所以可以用游程编码( 百度百科点这 )(其他博客点这)
定义的数组和他们的作用相信你看了那些就懂了(或者看看刘汝佳蓝色书的P198页,那有详解)
查询(L, R)结果为以下三部分最大值: L到L所在段的结点处的元素个数(即right[L]-L+1) , R到R所在段的开始处的元素个数(即R-left[R]+1), 中间第num[L]+1段到第num[R]-1段的count最大值
注意特殊情况: 若L和R在同一段,
代码:
ps: 由于评测不了,我不知道我写的对不对,希望有人看见后能提醒下,我一定回来,谢谢
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 100000+99
int val[MAX], countt[MAX], cnt;//分别表示第i段的数值和出现的次数
int num[MAX], left[MAX], right[MAX];//分别表示位置p所在边的编号,左右端点的位置
int a[MAX];
int d[MAX][32];//log2(n)
int isleft;//记录最左端的编号
int n,q;
void init() {
for(int i = 1; i <= cnt; i++) d[i][0] = countt[i];
for(int k = 1; (1<<k) <= cnt; k++)
for(int i = 1; i+(1<<k)-1 <= cnt; i++)
d[i][k] = max(d[i][k-1], d[i + (1<<(k-1))][k-1]);
}
int RMQ(int L, int R) {
int k = 0;
while(( 1<<(k+1) ) <= R-L+1) k++;
return max(d[L][k], d[R-(1<<k)+1][k]);
}
int main() {
while(scanf("%d%d",&n, &q) == 2) {
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i]);
if(a[i] > a[i-1]) {
cnt++;
val[cnt] = a[i];
countt[cnt]++;
num[i] = cnt;
left[i] = i;
for(int j = i-1; j >= 1 && num[j] == num[i-1]; j--) right[j] = i-1;//i-1所在的一段已完,处理right
for(int j = i-1; j >= isleft && isleft != 0; j--) left[j] = isleft;//处理left
isleft = i;
} else {
countt[cnt]++;
num[i] = cnt;
}
}
// for(int i = 1; i <= n; i++) {
// printf("第%d个数所在边的编号, 左端点, 右端点:
", i);
// printf(" %d %d %d
", num[i], left[i], right[i]);
// }//温馨提示: 注意差错哦
// printf("当前最左端: %d 当前的段数: %d", isleft, cnt);
for(int i = n; i >= isleft; i--) left[i] = isleft, right[i] = n;
// for(int i = 1; i <= n; i++) {
// printf("第%d个数所在边的编号, 左端点, 右端点:
", i);
// printf(" %d %d %d
", num[i], left[i], right[i]);
// }
init();
int L,R,ans = 0;
for(int i = 1; i <= q; i++) {
scanf("%d%d",&L, &R);
if(num[L] == num[R]) ans = R-L+1;
else {
ans = max(R-left[R]+1, right[L]-L+1);
if(num[L]+1 < num[R]) ans = max(ans, RMQ(num[L]+1, num[R]-1));
}
printf("%d
", ans);
}
}
return 0;
}
这里还给出刘汝佳的std代码(我没仔细看....
// UVa11235 Frequent Values
// Rujia Liu
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 100000 + 5;
const int maxlog = 20;
// 区间最*大*值
struct RMQ {
int d[maxn][maxlog];
void init(const vector<int>& A) {
int n = A.size();
for(int i = 0; i < n; i++) d[i][0] = A[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 0; i + (1<<j) - 1 < n; i++)
d[i][j] = max(d[i][j-1], d[i + (1<<(j-1))][j-1]);
}
int query(int L, int R) {
int k = 0;
while((1<<(k+1)) <= R-L+1) k++; // 如果2^(k+1)<=R-L+1,那么k还可以加1
return max(d[L][k], d[R-(1<<k)+1][k]);
}
};
int a[maxn], num[maxn], left[maxn], right[maxn];
RMQ rmq;
int main() {
int n, q;
while(scanf("%d%d", &n, &q) == 2) {
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
a[n] = a[n-1] + 1; // 哨兵
int start = -1;
vector<int> count;
for(int i = 0; i <= n; i++) {
if(i == 0 || a[i] > a[i-1]) { // 新段开始
if(i > 0) {
count.push_back(i - start);
for(int j = start; j < i; j++) {
num[j] = count.size() - 1; left[j] = start; right[j] = i-1;
}
}
start = i;
}
}
rmq.init(count);
while(q--) {
int L, R, ans;
scanf("%d%d", &L, &R); L--; R--;
if(num[L] == num[R]) ans = R-L+1;
else {
ans = max(R-left[R]+1, right[L]-L+1);
if(num[L]+1 < num[R]) ans = max(ans, rmq.query(num[L]+1, num[R]-1));
}
printf("%d
", ans);
}
}
return 0;
}