2025“钉耙编程”中国大学生算法设计春季联赛(1)1004 海浪
海浪问题题解
问题描述
给定一个长度为 n 的整数序列
其中每个数表示海面上某一块区域的波动高度。我们希望找到满足下面“海浪”性质的连续子区间:
存在某个实数基准值 (h_B) 使得对于子区间内所有相邻元素对
成立。换句话说,子区间内的波动要围绕 (h_B) 交替出现——可以是 “低–高–低–高…” 或 “高–低–高–低…”。
对于每个查询 ([l, r])(共 (q) 次查询),要求在区间 ([l, r]) 内找到满足条件的最长海浪子区间,记其长度为 (ans_i)。最后输出压缩答案:
解题思路
题目的关键在于如何快速判断一个区间是否构成海浪,以及如何在给定区间内查找最长的满足条件的子区间。总体思路分为以下几部分:
-
区间合法性判断
考察区间 ([l, r]) 是否存在一个基准值 (h_B) 使得相邻元素交替分布。
注意到,若将区间中元素按其相对“奇偶位置”分组(即相对于区间起点的顺序),则存在两种交替模式:- 模式1:子区间内第一个数较低,后面依次为低、高、低、高……
此时要求“低组”中的最大值小于“高组”的最小值。 - 模式2:子区间内第一个数较高,后面依次为高、低、高、低……
此时要求“低组”的最小值大于“高组”的最大值。
为了利用预处理加速查询,本题构造了针对全局下标的稀疏表。由于连续区间的相对奇偶性与全局奇偶有关(当区间起点奇偶不同,区间内的相对分组会交换),因此代码中同时判断:
even_min > odd_max(对应一种情况)odd_min > even_max(对应另一种情况)
如果两者中至少有一条成立,则认为区间 ([l, r]) 构成合法的海浪。
- 模式1:子区间内第一个数较低,后面依次为低、高、低、高……
-
预处理:稀疏表构造
为了快速获得任意区间内全局下标为奇数位置的最小值与最大值,以及偶数位置的最小值与最大值,代码分别构造了四个稀疏表:
st_odd_min和st_odd_max:全局奇数位置的极值st_even_min和st_even_max:全局偶数位置的极值
这样在区间查询时,利用预处理对数数组
lg[],能在 (O(1)) 时间内得到区间内的最值。 -
双指针预处理
使用双指针枚举每个右端点 (i)(从 1 到 (n)),寻找最左端的下标 (j) 使得区间 ([j, i]) 满足海浪条件。存入数组
pos[i],同时记录以 (i) 为右端点时的海浪长度(即 (i - j + 1)),保存在数组len中。随后对
len数组构造稀疏表,使得对于任意区间内能够快速查询最长海浪长度。 -
处理查询
对于每个查询 ([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;
}

浙公网安备 33010602011771号