数列区间
题目链接: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;
}