【ybt高效进阶4-3-1】【luogu P3865】数列区间 / 【模板】ST表

数列区间

题目链接: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+2j1] 这个区间的最值。
那容易得出转移方法:(其实是倍增的思想)
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,j1,fi+2j1,j1}
当然, 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 1n 的)。
然后你考虑这样能不能全部覆盖,那容易想到,两个都是长度超过一半的,放在两边,肯定会覆盖所有点。

那就可以了,每次询问 [ l , r ] [l,r] [l,r] 就先求出 s i z e = log ⁡ ( r − l + 1 ) size=\log(r-l+1) size=log(rl+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,fr2size+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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43346722/article/details/114992105
今日推荐