文章目录
- [一. 洛谷 [P3865 【模板】ST 表 & RMQ 问题](https://www.luogu.com.cn/problem/P3865)](#一. 洛谷 P3865 【模板】ST 表 & RMQ 问题)
-
- [1. 常规动态规划解法](#1. 常规动态规划解法)
-
- [(1) 算法原理](#(1) 算法原理)
- [(2) 代码](#(2) 代码)
- [2. ST表](#2. ST表)
-
- [(1) 算法原理](#(1) 算法原理)
- [(2) 代码](#(2) 代码)
- [二. 洛谷 [P2880 [USACO07JAN] Balanced Lineup G](https://www.luogu.com.cn/problem/P2880)](#二. 洛谷 P2880 [USACO07JAN] Balanced Lineup G)
-
- [1. 题目解析](#1. 题目解析)
- [2. 算法原理](#2. 算法原理)
- [3. 代码](#3. 代码)
⭐️ 博主在学习ST表时, 看的是b站的 STUACM-算法讲堂-ST表(区间最值问题)
一. 洛谷 P3865 【模板】ST 表 & RMQ 问题
翻译为人话 -> 求数组中, 任意区间的最大值
1. 常规动态规划解法
当数据量较少时, 区间最值查询可以使用常规的动态规划, 把所有区间的最大值都枚举了出来, 存放到了dp表总, 时间与空间复杂度为O(N^2), 查询的时间复杂度为O(1)
(1) 算法原理
(2) 代码
java
// 动态规划 i <= j, 区间[i,j]
public static void dpFun(int[] arr) throws IOException {
int n = arr.length;
int[][] dp = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (i == j) dp[i][j] = arr[j];
else dp[i][j] = Math.max(dp[i][j - 1], arr[j]);
}
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line);
System.out.println(dp[Integer.parseInt(st.nextToken())][Integer.parseInt(st.nextToken())]);
}
}
2. ST表
对于这种静态查询, 且重叠区间最终得到的结果一致, ST表一般是最佳解法, ST表是通过倍增思想, 对于dp表的一种时间和空间的优化, 只能解决特定场景, 时间与空间复杂度为O(N * logN), 查询的时间复杂度为O(1)
(1) 算法原理
从使用到创建ST表, 每个变量的含义也都在旁边标注
(2) 代码
因为在创建和使用ST表过程中会频繁使用log2向下取整这个函数, 因此要封装起来
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class ST {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer s = new StringTokenizer(br.readLine());
int n = Integer.parseInt(s.nextToken());
int m = Integer.parseInt(s.nextToken());
// 最大的无非就是从0开始延伸的,最接近末尾的,2的次幂的长度的区间, 可能包含log2(n)
int[][] st = new int[n][log2(n) + 1]; // 对应dp表 [i][i + 2^j -1] 左右端点
s = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
st[i][0] = Integer.parseInt(s.nextToken());//初始化dp表
}
// 填写dp表
//万万要注意,这里j表示区间的2^j长度,不是右端点
for(int j = 1; j <= log2(n); j++) {
// 这里i是左端点, (i + (1 << j) - 1) 表示的是区间的右端点, 使其<n, 意义是在i从0开始遍历时,确保加上2^j个长度后,右端点不越界,越界了就没有意义了
for (int i = 0; (i + (1 << j) - 1) < n; i++) {
st[i][j] = Math.max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
// 查询
StringBuilder sb = new StringBuilder();
for (int i = 0; i < m; i++) {
s = new StringTokenizer(br.readLine());
int l = Integer.parseInt(s.nextToken()) - 1; // 查询的区间下标==输入的数字-1
int r = Integer.parseInt(s.nextToken()) - 1;
int j = log2(r - l + 1);
sb.append(Math.max(st[l][j], st[r - (1 << j) + 1][j])).append('\n');
}
System.out.println(sb);
}
public static int log2(int num) {
return 31 - Integer.numberOfLeadingZeros(num);
}
}
二. 洛谷 P2880 [USACO07JAN] Balanced Lineup G
1. 题目解析
这道题翻译成人话, 就是求任意区间中, 最大值与最小值的差
2. 算法原理
这道题依旧是使用ST表, 可以建立两个st表, stMax用来储存任意2^n长度的区间的最大值, stMin用来储存任意2^n长度的区间的最小值
具体如何使用和建立ST表, 在上面的模版
3. 代码
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
// 经典的ST应用问题, 也可以使用线段树或者树状数组
public static void main(String[] args) throws Exception {
int n, q;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer s = new StringTokenizer(br.readLine());
n = Integer.parseInt(s.nextToken());
q = Integer.parseInt(s.nextToken());
int[][] stMin = new int[n][log2(n) + 1]; // st[i][j] -> dp[i][i + 2^j - 1]
int[][] stMax = new int[n][log2(n) + 1];
// 初始化st数组
for (int i = 0; i < n; i++) stMin[i][0] = stMax[i][0] = Integer.parseInt(new StringTokenizer(br.readLine()).nextToken());
// 填表
for (int j = 1; j <= log2(n); j++) {
// 以i为起点时, 右端点不能超过n
for (int i = 0; (i + (1 << j) - 1) < n; i++) {
stMin[i][j] = Math.min(stMin[i][j - 1], stMin[i + (1 << (j - 1))][j - 1]);
stMax[i][j] = Math.max(stMax[i][j - 1], stMax[i + (1 << (j - 1))][j - 1]);
}
}
// 查询
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q; i++) {
s = new StringTokenizer(br.readLine());
int l = Integer.parseInt(s.nextToken()) - 1;
int r = Integer.parseInt(s.nextToken()) - 1;
sb.append(queryMax(stMax, l, r) - queryMin(stMin, l, r)).append('\n');
}
System.out.println(sb);
}
static int log2(int num) {
return 31 - Integer.numberOfLeadingZeros(num);
}
static int queryMin(int[][] st, int l, int r) {
int j = log2((r - l + 1));
return Math.min(st[l][j], st[r - (1 << j) + 1][j]);
}
static int queryMax(int[][] st, int l, int r) {
int j = log2((r - l + 1));
return Math.max(st[l][j], st[r - (1 << j) + 1][j]);
}
}



