1 条题解
-
0
一、题目重述
给定长度为 的数组 ,以及 个查询 。
每个查询要求:即区间 内任意两个不同元素的最小绝对差。
数据范围:,,。
二、核心难点
- 区间最小差问题,不能直接套用 RMQ
- 需要高效回答大量区间查询()
- 支持离线,不支持在线修改
三、关键性质
性质 1:若我们固定右端点 ,则对于每个左端点 , 的最小差值可以递推维护。
性质 2:当右端点从 移动到 时,只有与新元素 最接近的两个元素(前驱和后继)会影响之前的所有左端点 。
性质 3:前驱:小于 的最大值;后继:大于 的最小值。
因为任意其他元素与 的差 ≥ 前驱/后继与 的差。
四、算法思路:离线 + 线段树
4.1 基本框架
- 将所有查询按右端点 从小到大排序。
- 维护一个线段树,其中 表示当前右端点下,以 为左端点的区间 的最小差值。
- 当右端点从 移动到 时:
- 找到 在已出现元素中的前驱和后继
- 对于每个前驱/后继的位置 ,区间 的左端点都可以用 更新(因为 产生了新的更小差值)
- 用线段树的区间更新(取最小值)来维护
- 处理所有右端点 的查询,答案 = 线段树区间 的最小值。
4.2 为什么只更新前驱和后继
设前驱位置为 ,值为 。
对于任意左端点 ,区间 包含 和 ,因此差值 。
而且 是所有 与左边元素的差值中最小的,所以只更新它即可。同理处理后继。
4.3 线段树的维护
- 线段树每个节点维护对应区间的最小差值
- 支持操作:
- 单点更新(将 更新为 )
实际上这里是对左端点 的更新, 范围是 ,所以是区间取最小值。
- 单点更新(将 更新为 )
- 查询:区间最小值
五、复杂度分析
- 排序查询:
- 每个 找前驱后继:使用
std::map或std::set, - 每次更新:线段树区间取最小值
- 每个查询:
- 总复杂度:,对于 完全可行。
六、最终代码(标程)
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int M = 3e5 + 5; const int INF = 1e9 + 10; int n, m; int a[N]; int ans[M]; struct Query { int l, r, id; bool operator<(const Query& other) const { return r < other.r; } } q[M]; // 线段树:维护区间最小值 int tr[N << 2]; void build(int u, int l, int r) { tr[u] = INF; if (l == r) return; int mid = (l + r) >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); } void update(int u, int l, int r, int pos, int val) { if (l == r) { tr[u] = min(tr[u], val); return; } int mid = (l + r) >> 1; if (pos <= mid) update(u << 1, l, mid, pos, val); else update(u << 1 | 1, mid + 1, r, pos, val); tr[u] = min(tr[u << 1], tr[u << 1 | 1]); } int query(int u, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return tr[u]; int mid = (l + r) >> 1; int res = INF; if (ql <= mid) res = min(res, query(u << 1, l, mid, ql, qr)); if (qr > mid) res = min(res, query(u << 1 | 1, mid + 1, r, ql, qr)); return res; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; cin >> m; for (int i = 0; i < m; ++i) { cin >> q[i].l >> q[i].r; q[i].id = i; } sort(q, q + m); // 记录每个值最近出现的位置 map<int, int> last_pos; build(1, 1, n); int idx = 0; for (int r = 1; r <= n; ++r) { int x = a[r]; // 找前驱(小于 x 的最大值) auto it = last_pos.lower_bound(x); if (it != last_pos.end()) { update(1, 1, n, it->second, it->first - x); } // 找后继(大于 x 的最小值) if (it != last_pos.begin()) { --it; update(1, 1, n, it->second, x - it->first); } last_pos[x] = r; // 处理所有右端点为 r 的查询 while (idx < m && q[idx].r == r) { ans[q[idx].id] = query(1, 1, n, q[idx].l, r); ++idx; } } for (int i = 0; i < m; ++i) { cout << ans[i] << "\n"; } return 0; }
七、正确性说明
- 当右端点固定为 时,线段树 存储了区间 的最小差值
- 每次加入新元素 ,只可能通过与其最接近的两个元素产生更小的差值
- 更新这些差值到所有包含这两个元素位置的左端点
- 查询时直接取区间最小值
八、注意事项
- 使用
map维护每个值最近出现的位置,保证前驱后继的 查找 - 线段树采用单点取 min 更新(因为每个位置只会被更新有限次)
- 初始化线段树为
INF,表示未计算
- 1
信息
- ID
- 7245
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者