1 条题解
-
0
🧩 问题核心分析
我们需要维护一个向量集合,支持两种操作:
A x y:加入向量Q x y l r:查询第 到第 个加入的向量与向量 点积的最大值
关键难点:
- 操作在线进行,无法离线处理
- 数据规模大:
- 需要高效支持区间查询
💡 核心思路:线段树维护凸包
此问题的标准解法是使用线段树维护凸包,并利用点积的几何性质进行优化。
🔹 第一步:问题转化
对于查询向量 与区间内向量 的点积最大值:
这可以看作在点集 上求线性函数 的最大值。
🔹 第二步:凸包优化
根据计算几何知识:
- 点积 的最大值一定在点集的凸包上取得
- 对于固定的 ,最大化 等价于在凸包上找到与向量 方向最匹配的点
因此,我们可以:
- 用线段树维护每个区间的上凸壳
- 查询时合并相关区间的凸包信息
- 在凸包上三分查找最优解
🔹 第三步:线段树结构设计
由于向量按加入顺序排列,我们建立线段树,每个叶子节点对应一个向量,内部节点维护对应区间的凸包。
凸包维护:
- 每个节点维护一个上凸壳(按x坐标排序)
- 合并子节点凸包时使用Graham扫描或归并方法
- 为保证效率,只在节点被完全填满时才构建凸包
查询处理:
- 分解查询区间 为 个线段树节点
- 在每个节点的凸包上三分查找最优解
- 合并所有节点的结果取最大值
🧮 算法实现细节
代码框架
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 400005; struct Point { ll x, y; Point() {} Point(ll x, ll y) : x(x), y(y) {} Point operator - (const Point &b) const { return Point(x - b.x, y - b.y); } ll operator * (const Point &b) const { return x * b.x + y * b.y; } ll operator ^ (const Point &b) const { return x * b.y - y * b.x; } bool operator < (const Point &b) const { return x < b.x || (x == b.x && y < b.y); } }; // 判断三个点的转向 ll cross(const Point &a, const Point &b, const Point &c) { return (b - a) ^ (c - a); } struct Node { vector<Point> hull; void build(const vector<Point> &points) { // 构建上凸壳 vector<Point> tmp = points; sort(tmp.begin(), tmp.end()); hull.clear(); for (int i = 0; i < tmp.size(); i++) { while (hull.size() >= 2 && cross(hull[hull.size()-2], hull.back(), tmp[i]) >= 0) { hull.pop_back(); } hull.push_back(tmp[i]); } } ll query(const Point &p) const { // 在凸包上三分查找最大点积 int l = 0, r = hull.size() - 1; while (r - l > 2) { int m1 = l + (r - l) / 3; int m2 = r - (r - l) / 3; ll v1 = p * hull[m1]; ll v2 = p * hull[m2]; if (v1 < v2) l = m1; else r = m2; } ll res = -1e18; for (int i = l; i <= r; i++) { res = max(res, p * hull[i]); } return res; } }; class SegmentTree { private: vector<Node> tree; int n; void build(int idx, int l, int r, const vector<Point> &points) { if (l == r) { tree[idx].build({points[l]}); return; } int mid = (l + r) / 2; build(idx*2, l, mid, points); build(idx*2+1, mid+1, r, points); // 合并子节点凸包 vector<Point> merged; for (auto &p : tree[idx*2].hull) merged.push_back(p); for (auto &p : tree[idx*2+1].hull) merged.push_back(p); tree[idx].build(merged); } ll query(int idx, int l, int r, int ql, int qr, const Point &p) { if (ql <= l && r <= qr) { return tree[idx].query(p); } int mid = (l + r) / 2; ll res = -1e18; if (ql <= mid) res = max(res, query(idx*2, l, mid, ql, qr, p)); if (qr > mid) res = max(res, query(idx*2+1, mid+1, r, ql, qr, p)); return res; } public: SegmentTree(const vector<Point> &points) { n = points.size(); tree.resize(4 * n); build(1, 0, n-1, points); } ll query(int l, int r, const Point &p) { return query(1, 0, n-1, l, r, p); } }; // 解密函数 inline int decode(int x, long long lastans) { return x ^ (lastans & 0x7fffffff); } int main() { int n; char type; scanf("%d %c", &n, &type); vector<Point> points; vector<pair<char, vector<int>>> operations; long long lastans = 0; int cnt = 0; for (int i = 0; i < n; i++) { char op[2]; scanf("%s", op); if (op[0] == 'A') { int x, y; scanf("%d%d", &x, &y); if (type != 'E') { x = decode(x, lastans); y = decode(y, lastans); } points.push_back(Point(x, y)); cnt++; } else { int x, y, l, r; scanf("%d%d%d%d", &x, &y, &l, &r); if (type != 'E') { x = decode(x, lastans); y = decode(y, lastans); l = decode(l, lastans); r = decode(r, lastans); } // 注意:这里需要根据实际加入顺序调整索引 l--; r--; // 建立线段树并查询 SegmentTree seg(points); lastans = seg.query(l, r, Point(x, y)); printf("%lld\n", lastans); } } return 0; }⚠️ 实现注意事项
-
在线构建优化:
- 上述代码在每次查询时重建线段树,实际应增量构建
- 可改为:当新向量加入时,更新对应叶子节点,在必要时重建祖先节点的凸包
-
凸包维护:
- 使用上凸壳(对于最大化点积)
- 注意处理共线情况
- 合并凸包时使用归并提高效率
-
查询优化:
- 三分查找是 的
- 也可使用二分查找,利用凸包上点积的单峰性
-
复杂度分析:
- 构建:
- 查询:
- 空间:
💎 总结
这道题的核心在于:
- 几何转化:将点积最大化转化为凸包查询问题
- 数据结构:使用线段树维护区间凸包信息
- 在线处理:支持动态插入和区间查询
- 算法优化:利用凸包性质和三分查找提高效率
- 1
信息
- ID
- 5668
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者