2025“钉耙编程”中国大学生算法设计春季联赛(1)1004 海浪

海浪问题题解

问题描述

给定一个长度为 n 的整数序列

\[a_1, a_2, \dots, a_n, \]

其中每个数表示海面上某一块区域的波动高度。我们希望找到满足下面“海浪”性质的连续子区间:

存在某个实数基准值 (h_B) 使得对于子区间内所有相邻元素对

\[(h_{i-1} - h_B) \cdot (h_i - h_B) < 0 \quad (i = l+1, l+2, \dots, r) \]

成立。换句话说,子区间内的波动要围绕 (h_B) 交替出现——可以是 “低–高–低–高…” 或 “高–低–高–低…”。

对于每个查询 ([l, r])(共 (q) 次查询),要求在区间 ([l, r]) 内找到满足条件的最长海浪子区间,记其长度为 (ans_i)。最后输出压缩答案:

\[R = \left(\sum_{i=1}^{q} i \times ans_i \right) \mod (10^9+7). \]

解题思路

题目的关键在于如何快速判断一个区间是否构成海浪,以及如何在给定区间内查找最长的满足条件的子区间。总体思路分为以下几部分:

  1. 区间合法性判断

    考察区间 ([l, r]) 是否存在一个基准值 (h_B) 使得相邻元素交替分布。
    注意到,若将区间中元素按其相对“奇偶位置”分组(即相对于区间起点的顺序),则存在两种交替模式:

    • 模式1:子区间内第一个数较低,后面依次为低、高、低、高……
      此时要求“低组”中的最大值小于“高组”的最小值。
    • 模式2:子区间内第一个数较高,后面依次为高、低、高、低……
      此时要求“低组”的最小值大于“高组”的最大值。

    为了利用预处理加速查询,本题构造了针对全局下标的稀疏表。由于连续区间的相对奇偶性与全局奇偶有关(当区间起点奇偶不同,区间内的相对分组会交换),因此代码中同时判断:

    • even_min > odd_max(对应一种情况)
    • odd_min > even_max(对应另一种情况)

    如果两者中至少有一条成立,则认为区间 ([l, r]) 构成合法的海浪。

  2. 预处理:稀疏表构造

    为了快速获得任意区间内全局下标为奇数位置的最小值与最大值,以及偶数位置的最小值与最大值,代码分别构造了四个稀疏表:

    • st_odd_minst_odd_max:全局奇数位置的极值
    • st_even_minst_even_max:全局偶数位置的极值

    这样在区间查询时,利用预处理对数数组 lg[],能在 (O(1)) 时间内得到区间内的最值。

  3. 双指针预处理

    使用双指针枚举每个右端点 (i)(从 1 到 (n)),寻找最左端的下标 (j) 使得区间 ([j, i]) 满足海浪条件。存入数组 pos[i],同时记录以 (i) 为右端点时的海浪长度(即 (i - j + 1)),保存在数组 len 中。

    随后对 len 数组构造稀疏表,使得对于任意区间内能够快速查询最长海浪长度。

  4. 处理查询

    对于每个查询 ([l, r]):

    • 利用预处理得到的 pos 数组,采用二分查找(通过 lower_bound)找出第一个使得海浪起点在查询区间内部的位置。
    • 根据情况分别从两部分取最大值:一部分是以起点在查询区间左侧的海浪,另一部分是查询区间内部利用 len 稀疏表获得的最大海浪长度。

代码实现

下面给出完整代码(带有详细注释),并在最后计算出压缩答案。

#include <bits/stdc++.h>
#define int long long
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;

const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int lg[N];  // 用于快速查询区间长度对应的对数值

// 快速幂及逆元函数(本题中未使用 inv2 但保留了 qpw/inv)
int qpw(int a, int b) {
    int ans = 1;
    while(b) {
        if(b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
int inv(int x) { return qpw(x, mod - 2); }

//
// 稀疏表部分
// st_odd_min / st_odd_max 用于记录全局奇数位置的极值;
// st_even_min / st_even_max 用于记录全局偶数位置的极值。
//
int st_odd_min[N][30], st_even_min[N][30];
int st_odd_max[N][30], st_even_max[N][30];

//
// check(l, r) 用于判断区间 [l, r] 是否构成海浪。
// 计算区间内全局奇数位置和偶数位置的最值,
// 若满足 either (even_min > odd_max) 或 (odd_min > even_max) 则返回 true.
//
bool check(int l, int r) {
    int k = lg[r - l + 1];
    int odd_min = min(st_odd_min[l][k], st_odd_min[r - (1 << k) + 1][k]);
    int odd_max = max(st_odd_max[l][k], st_odd_max[r - (1 << k) + 1][k]);
    int even_min = min(st_even_min[l][k], st_even_min[r - (1 << k) + 1][k]);
    int even_max = max(st_even_max[l][k], st_even_max[r - (1 << k) + 1][k]);
    return even_min > odd_max || odd_min > even_max;
}

//
// pos[i] 表示以 i 为右端点的最长海浪对应的最左起点;
// len[i][0] 存储以 i 为右端点时的海浪长度(即 i - pos[i] + 1)。
//
int len[N][30], pos[N];

//
// query(l, r) 利用对 len 数组构造的稀疏表查询区间 [l, r] 内的最大海浪长度。
//
int query(int l, int r) {
    int k = lg[r - l + 1];
    return max(len[l][k], len[r - (1 << k) + 1][k]);
}

//
// 主求解函数
//
void solve() {
    int n, q;
    cin >> n >> q;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    
    // 初始化稀疏表的第0层
    // 对于每个位置,若其下标为奇数则将奇数表置为 a[i],偶数表取极值(反之亦然)
    for (int i = 1; i <= n; i++) {
        st_odd_max[i][0] = st_even_max[i][0] = -1e9 - 1;
        st_odd_min[i][0] = st_even_min[i][0] = 1e9 + 1;
        if (i & 1) {
            st_odd_max[i][0] = st_odd_min[i][0] = a[i];
        } else {
            st_even_max[i][0] = st_even_min[i][0] = a[i];
        }
    }
    // 构造稀疏表,j 为区间长度为 2^j 时的信息
    for (int j = 1; j <= 20; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            st_odd_max[i][j] = max(st_odd_max[i][j-1], st_odd_max[i + (1 << (j-1))][j-1]);
            st_even_max[i][j] = max(st_even_max[i][j-1], st_even_max[i + (1 << (j-1))][j-1]);
            st_odd_min[i][j] = min(st_odd_min[i][j-1], st_odd_min[i + (1 << (j-1))][j-1]);
            st_even_min[i][j] = min(st_even_min[i][j-1], st_even_min[i + (1 << (j-1))][j-1]);
        }
    }
    
    // 双指针预处理:
    // 对于每个右端点 i,寻找最小的 j 使得区间 [j, i] 满足海浪条件
    for (int i = 1, j = 1; i <= n; i++) {
        while(j <= i && !check(j, i))
            j++;
        pos[i] = j;
        len[i][0] = i - j + 1;
    }
    
    // 构造 len 数组的稀疏表,用于区间查询
    for (int i = 1; i <= 20; i++) {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) {
            len[j][i] = max(len[j][i-1], len[j + (1 << (i-1))][i-1]);
        }
    }
    
    // 处理每个查询
    // 利用 pos 数组和 len 稀疏表结合二分查找得到查询区间内最长海浪的长度
    vector<int> ans(q + 1);
    for (int i = 1; i <= q; i++) {
        int l, r;
        cin >> l >> r;
        // 在 pos[l...r] 中查找第一个位置,使得 pos[x] >= l
        int x = lower_bound(pos + l, pos + r + 1, l) - pos;
        ans[i] = max(ans[i], x - l);
        if(x <= r)
            ans[i] = max(ans[i], query(x, r));
    }
    
    // 计算压缩答案 R = (∑ i * ans[i]) mod (1e9+7)
    int res = 0;
    for (int i = 1; i <= q; i++) {
        res = (res + i * ans[i] % mod) % mod;
    }
    cout << res << '\n';
}

signed main() {
    // 预处理 lg 数组,lg[i] = floor(log2(i))
    for (int i = 2; i <= 1e5; i++)
        lg[i] = lg[i / 2] + 1;
    
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    
    int T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}
posted @ 2025-03-11 15:53  archer2333  阅读(134)  评论(0)    收藏  举报