1 条题解

  • 0
    @ 2025-11-27 15:57:16

    🧩 问题核心分析

    我们需要维护一个向量集合,支持两种操作:

    • A x y:加入向量 (x,y)(x, y)
    • Q x y l r:查询第 ll 到第 rr 个加入的向量与向量 (x,y)(x, y) 点积的最大值

    关键难点

    • 操作在线进行,无法离线处理
    • 数据规模大:N4×105N \leq 4 \times 10^5
    • 需要高效支持区间查询

    💡 核心思路:线段树维护凸包

    此问题的标准解法是使用线段树维护凸包,并利用点积的几何性质进行优化。

    🔹 第一步:问题转化

    对于查询向量 (x,y)(x, y) 与区间内向量 (zi,wi)(z_i, w_i) 的点积最大值:

    maxi=lr(xzi+ywi)\max_{i=l}^r (x \cdot z_i + y \cdot w_i)

    这可以看作在点集 (zi,wi){(z_i, w_i)} 上求线性函数 f(z,w)=xz+ywf(z,w) = xz + yw 的最大值。

    🔹 第二步:凸包优化

    根据计算几何知识:

    • 点积 xz+ywxz + yw 的最大值一定在点集的凸包上取得
    • 对于固定的 (x,y)(x, y),最大化 xz+ywxz + yw 等价于在凸包上找到与向量 (x,y)(x, y) 方向最匹配的点

    因此,我们可以:

    1. 用线段树维护每个区间的上凸壳
    2. 查询时合并相关区间的凸包信息
    3. 在凸包上三分查找最优解

    🔹 第三步:线段树结构设计

    由于向量按加入顺序排列,我们建立线段树,每个叶子节点对应一个向量,内部节点维护对应区间的凸包。

    凸包维护

    • 每个节点维护一个上凸壳(按x坐标排序)
    • 合并子节点凸包时使用Graham扫描归并方法
    • 为保证效率,只在节点被完全填满时才构建凸包

    查询处理

    • 分解查询区间 [l,r][l, r]O(logN)O(\log N) 个线段树节点
    • 在每个节点的凸包上三分查找最优解
    • 合并所有节点的结果取最大值

    🧮 算法实现细节

    代码框架

    #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. 在线构建优化

      • 上述代码在每次查询时重建线段树,实际应增量构建
      • 可改为:当新向量加入时,更新对应叶子节点,在必要时重建祖先节点的凸包
    2. 凸包维护

      • 使用上凸壳(对于最大化点积)
      • 注意处理共线情况
      • 合并凸包时使用归并提高效率
    3. 查询优化

      • 三分查找是 O(logSIZE)O(\log SIZE)
      • 也可使用二分查找,利用凸包上点积的单峰性
    4. 复杂度分析

      • 构建:O(NlogN)O(N \log N)
      • 查询:O(log2N)O(\log^2 N)
      • 空间:O(NlogN)O(N \log N)

    💎 总结

    这道题的核心在于:

    1. 几何转化:将点积最大化转化为凸包查询问题
    2. 数据结构:使用线段树维护区间凸包信息
    3. 在线处理:支持动态插入和区间查询
    4. 算法优化:利用凸包性质和三分查找提高效率
    • 1

    信息

    ID
    5668
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者