题解:洛谷 P2880 Balanced Lineup

【题目来源】

洛谷:P2880 [USACO07JAN] Balanced Lineup G - 洛谷

【题目描述】

每天,农夫 John 的 \(n\ (1\le n\le 5\times 10^4)\) 头牛总是按同一序列排队。

有一天,John 决定让一些牛们玩一场飞盘比赛。他准备找一群在队列中位置连续的牛来进行比赛。但是为了避免水平悬殊,牛的身高不应该相差太大。John 准备了 \(q\ (1\le q\le 1.8\times10^5)\) 个可能的牛的选择和所有牛的身高 \(h_i\ (1\le h_i\le 10^6,1\le i\le n)\)。他想知道每一组里面最高和最低的牛的身高差。

【输入】

第一行两个数 \(n,q\)

接下来 \(n\) 行,每行一个数 \(h_i\)

再接下来 \(q\) 行,每行两个整数 \(a\)\(b\),表示询问第 \(a\) 头牛到第 \(b\) 头牛里的最高和最低的牛的身高差。

【输出】

输出共 \(q\) 行,对于每一组询问,输出每一组中最高和最低的牛的身高差。

【输入样例】

6 3
1
7
3
4
2
5
1 5
4 6
2 2

【输出样例】

6
3
0

【核心思想】

  1. 问题分析:给定 \(n\) 头奶牛的身高序列和 \(q\) 个区间查询,每个查询求区间 \([a, b]\) 内最高牛与最矮牛的身高差(极差)。这是一个区间最值查询(RMQ)问题,关键在于高效回答大量查询,ST表预处理实现 \(O(1)\) 查询。

  2. 算法选择

    • ST表(Sparse Table):一种基于倍增思想的静态数据结构,预处理 \(O(n \log n)\),查询 \(O(1)\)
    • 双ST表:分别维护区间最大值和最小值,查询时同时获取两者后求差
  3. 关键步骤

    • 读取输入\(n, q\) 和身高数组 \(h[1..n]\)
    • ST表初始化
      • \(a[i][0] = b[i][0] = h[i]\)(长度为 \(2^0 = 1\) 的区间最值即自身)
    • ST表构建\(j\)\(1\)\(\lfloor \log_2 n \rfloor\)):
      • 区间长度 \(len = 2^j\)
      • \(a[i][j] = \max(a[i][j-1],\ a[i+2^{j-1}][j-1])\):左半段与右半段的最大值
      • \(b[i][j] = \min(b[i][j-1],\ b[i+2^{j-1}][j-1])\):左半段与右半段的最小值
    • 查询处理(每个查询 \([l, r]\)):
      • \(k = \lfloor \log_2(r-l+1) \rfloor\)
      • 最大值 = \(\max(a[l][k],\ a[r-2^k+1][k])\):两个重叠区间的最大值
      • 最小值 = \(\min(b[l][k],\ b[r-2^k+1][k])\):两个重叠区间的最小值
      • 输出 最大值 \(-\) 最小值
  4. 时间/空间复杂度

    • 预处理时间:\(O(n \log n)\)
    • 单次查询时间:\(O(1)\)
    • 总查询时间:\(O(q)\)
    • 空间复杂度:\(O(n \log n)\),两个ST表
  5. ST表的核心思想

    • 倍增分治:将区间 \([i, i+2^j-1]\) 拆分为两个长度为 \(2^{j-1}\) 的子区间,通过已计算的子区间最值合并得到当前区间最值
    • 重叠区间查询:查询时取 \(k = \lfloor \log_2(len) \rfloor\),用两个长度为 \(2^k\) 的重叠区间完全覆盖查询区间。由于最值运算具有幂等性(重叠不影响结果),可直接合并
    • 静态数据结构优势:数组不变时,ST表预处理一次即可 \(O(1)\) 回答任意查询,适合查询量远大于修改量的场景
    • 适用于"静态数组 + 大量区间最值查询"类问题,核心是倍增预处理和重叠区间覆盖

【算法标签】

普及 #ST表

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 定义常量
const int N = 50005;  // 最大数据量

// 定义数组
int a[N][20];  // ST表,用于存储区间最大值,a[i][j]表示从i开始长度为2^j的区间最大值
int b[N][20];  // ST表,用于存储区间最小值,b[i][j]表示从i开始长度为2^j的区间最小值
int n;         // 数据个数
int q;         // 查询次数
int maxn;      // 临时变量,存储区间最大值
int minn;      // 临时变量,存储区间最小值

int main()
{
    // 读入数据个数和查询次数
    cin >> n >> q;

    // 读入初始数据并初始化ST表的第一层
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i][0];  // 输入第i个数
        b[i][0] = a[i][0];  // 同时赋值给最小值表
    }

    // 构建ST表
    for (int j = 1; j <= 20; j++)  // j表示区间长度为2^j
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i++)  // 确保区间不越界
        {
            // 计算区间[i, i+2^j-1]的最大值
            // 拆分为两个区间:[i, i+2^(j-1)-1] 和 [i+2^(j-1), i+2^j-1]
            a[i][j] = max(a[i][j - 1], a[i + (1 << (j - 1))][j - 1]);

            // 计算区间[i, i+2^j-1]的最小值
            b[i][j] = min(b[i][j - 1], b[i + (1 << (j-1))][j - 1]);
        }
    }

    // 处理查询
    while (q--)
    {
        int l, r;  // 查询区间[l, r]
        cin >> l >> r;

        // 计算区间长度的对数向下取整
        int k = log2(r - l + 1);

        // 查询区间最大值
        // 将区间[l, r]拆分为两个有重叠的区间:
        // [l, l+2^k-1] 和 [r-2^k+1, r]
        maxn = max(a[l][k], a[r - (1 << k) + 1][k]);

        // 查询区间最小值
        minn = min(b[l][k], b[r - (1 << k) + 1][k]);

        // 输出区间极差(最大值减最小值)
        cout << maxn - minn << endl;
    }

    return 0;
}

【运行结果】

6 3
1
7
3
4
2
5
1 5
6
4 6
3
2 2
0
posted @ 2026-08-28 15:55  团爸讲算法  阅读(2)  评论(0)    收藏  举报