数列区间
题目链接:ybt高效进阶4-3-1 / luogu P3865
题目大意
给你一个数列,多次询问,每次问一个区间中的最大值。
思路
这道题第一反应是线段树,然而常数太大过不了。
我们考虑用一个叫做 ST 表的东西,它主要是用倍增加 DP 的思想,求出一个区间的最大值。
然后因为这种 RMQ(求最值)的问题可以多次拿里面的一个数与最优答案比较,我们就利用这个性质,来做。
首先,我们定义
f
i
,
j
f_{i,j}
fi,j 为
[
i
,
i
+
2
j
−
1
]
[i,i+2^j-1]
[i,i+2j−1] 这个区间的最值。
那容易得出转移方法:(其实是倍增的思想)
f
i
,
j
=
max
{
f
i
,
j
−
1
,
f
i
+
2
j
−
1
,
j
−
1
}
f_{i,j}=\max\{f_{i,j-1},f_{i+2^{j-1},j-1}\}
fi,j=max{fi,j−1,fi+2j−1,j−1}
当然,
f
i
,
0
=
a
i
f_{i,0}=a_i
fi,0=ai。
然后你考虑查询一个区间的最大值要怎么搞。
那我们可以知道,因为只是求最大值,我们可以选择已知的一些区间时可以有覆盖,但是它们组合起来一定要包含你求所有的点,而且也不能多。
那你考虑从左边起选一个最长而不超过的区间,右边为终点选一个最长而不超过的区间。
那你考虑你可以直接得到长度为
2
x
2^x
2x 的每个区间的最大值,那你就找一个最大的
x
x
x,就要用到
log
\log
log,因为要用很多次,我们考虑直接线性预处理求出
log
\log
log(反正只用
1
∼
n
1\sim n
1∼n 的)。
然后你考虑这样能不能全部覆盖,那容易想到,两个都是长度超过一半的,放在两边,肯定会覆盖所有点。
那就可以了,每次询问 [ l , r ] [l,r] [l,r] 就先求出 s i z e = log ( r − l + 1 ) size=\log(r-l+1) size=log(r−l+1),然后答案就是 max { f l , s i z e , f r − 2 s i z e + 1 , s i z e } \max\{f_{l,size},f_{r-2^{size}+1,size}\} max{fl,size,fr−2size+1,size} 。
代码
#include<cstdio>
#include<iostream>
using namespace std;
int n, m, a[100001], x, y;
int log[100001], f[100001][17];
void get_log() {//预处理出 log
int now = 2, i, noww = 2;
for (i = 1; noww * 2 <= 100000; i++) {
while (now < noww * 2) {
log[now] = i;
now++;
}
noww <<= 1;
}
while (now <= 100000) {
log[now] = i;
now++;
}
}
int main() {
get_log();
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
f[i][0] = a[i];
}
for (int i = 1; i <= log[n]; i++)//构建 ST 表
for (int j = 1; j <= n; j++)
if (j + (1 << (i - 1)) > n) f[j][i] = f[j][i - 1];
else f[j][i] = max(f[j][i - 1], f[j + (1 << (i - 1))][i - 1]);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
int size = log[y - x + 1];//询问
printf("%d\n", max(f[x][size], f[y - (1 << size) + 1][size]));
}
return 0;
}