跳至主要內容

ST 表

大约 2 分钟

ST 表

题目难度
LQ240629T5 龙骑士军团

引入

ST 表基于 倍增 思想,可以做到 O(n log n) 预处理,O(1) 回答每个询问。但是不支持修改操作。

LQ240629T5 龙骑士军团

package lq240629;

import java.util.Scanner;

public class LQ240629T5 {
    static int n, q;
    static long[] v;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        q = scanner.nextInt();
        v = new long[n + 1];
        for (int i = 1; i <= n; ++i) {
            v[i] = scanner.nextInt();
            v[i] += v[i - 1];
        }
        SparseTable st = new SparseTable(v);
        StringBuilder output = new StringBuilder();
        for (int i = 0; i < q; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            int c = scanner.nextInt();
            int d = scanner.nextInt();
            long ans = 0;
            if (b < c) {
                ans += v[c - 1] - v[b];
            }
            long lv = v[b] - st.query_min(a - 1, b - 1);
            long rv = st.query_max(c, d) - v[c - 1];
            output.append((ans + lv + rv)).append(System.lineSeparator());
        }
        System.out.println(output);
    }

    private static String solve() {
        return "";
    }

    static class SparseTable {
        long[][] mx, mi;
        int[] logTable;

        public SparseTable(long[] arr) {
            int n = arr.length;
            int maxLog = log2(n) + 1;
            mx = new long[n][maxLog];
            mi = new long[n][maxLog];
            logTable = new int[n + 1];
            // 预处理对数表
            for (int i = 2; i <= n; ++i) {
                logTable[i] = logTable[i / 2] + 1;
            }
            // 初始化 ST 表
            for (int i = 0; i < n; ++i) {
                mx[i][0] = arr[i];
                mi[i][0] = arr[i];
            }
            // 动态规划填充表
            for (int j = 1; (1 << j) <= n; ++j) {
                for (int i = 0; i + (1 << j) <= n; ++i) {
                    mx[i][j] = Math.max(mx[i][j - 1], mx[i + (1 << (j - 1))][j - 1]);
                    mi[i][j] = Math.min(mi[i][j - 1], mi[i + (1 << (j - 1))][j - 1]);
                }
            }
        }

        // ceil(log2(x)) = 32 - numberOfLeadingZeros(x - 1)
        int log2(long x) {
            return 64 - Long.numberOfLeadingZeros(x - 1);
        }

        long query_max(int l, int r) {
            int j = logTable[r - l + 1];
            return Math.max(mx[l][j], mx[r - (1 << j) + 1][j]);
        }

        long query_min(int l, int r) {
            int j = logTable[r - l + 1];
            return Math.min(mi[l][j], mi[r - (1 << j) + 1][j]);
        }
    }
}
/*
龙骑士军团【算法赛】

首选得到原数组的前缀和数组 s。
对于一次查询 a,b,c,d,区间 [b+1, c-1] 的和是一定会被选上的,我们可以通过 s_{c-1} - s_b 快速计算得到。
接下来我们需要从区间 [a, b] 和 [c, d] 分别选一段后缀和一段前缀和。
- 假设从区间 [a, b] 选择了一个点 x,即答案贡献增加 s_b - s_{x-1}。
- 假设从区间 [c, d] 选择了一个点 y,即答案贡献增加 s_y - s_{c-1}。
显然从贪心考虑,我们希望最小化 s_{x-1} 和最大化 s_y,使用一个可支持查询区间静态最大值的数据结构即可。
 */

(全文完)