P3309 [SDOI2014] 向量集 题解

Description

维护一个向量集合,在线支持以下操作:

  • A x y\(|x|,|y| \le 10^8\)):加入向量 \((x,y)\)
  • Q x y l r\(|x|,|y| \le 10^8\)\(1 \le l \le r \le t\),其中 \(t\) 为已经加入的向量个数):询问第 \(l\) 个到第 \(r\) 个加入的向量与向量 \((x,y)\) 的点积的最大值。

集合初始时为空,操作次数 \(\leq 4\times 10^5\)

Solution

不妨设查询时最优的向量为 \((a,b)\),那么 \((a,b)\cdot (x,y)=ax+by=y\left(\frac{x}{y}\cdot a+b\right)\)

括号里面的东西是一个关于 \((a,b)\) 的一次函数,所以可以先求出这个区间的凸包然后在凸包上二分(注意这里 \(b\) 可能是负数,所以需要同时维护上凸壳和下凸壳)。

考虑怎么求出凸包。

容易发现可以维护一个线段树,对于线段树上每个节点维护这个节点对应区间目前加入的点的凸包。

由于每个点只会被加入到 \(O(\log n)\) 个节点,所以可以直接平衡树维护每个节点的凸包,时间复杂度是 \(O(n\log^2n)\),常数较大且过于复杂,考虑更好的做法。

上面做法的问题在于每次需要对于凸包实现动态插入,而维护凸包更好的做法是静态建凸包。

注意到每次查询访问到的节点一定被插满了,即这些节点一定不会再被修改。

所以对于每次修改,只需要找到所有已经被插满的节点然后对于这些节点静态建凸包即可。

这样常数就小很多了。

时间复杂度:\(O(n\log^2n)\)

Code

#include <bits/stdc++.h>

#define int int64_t

using pii = std::pair<int, int>;

const int kMaxN = 4e5 + 5;

int n, q;
std::string O;
std::vector<pii> vec[kMaxN * 4], v[kMaxN * 4][2];

/*
  v[x][0/1] : 线段树 x 号节点的上/下凸包
*/

pii sub(pii a, pii b) { return {a.first - b.first, a.second - b.second}; }
int mul(pii a, pii b) { return a.first * b.second - a.second * b.first; }
int func(pii a, pii b) { return a.first * b.first + a.second * b.second; }
int getid(int dep, int x) { return (1 << (19 - dep)) + x; }

std::vector<pii> getvec(std::vector<pii> vec) {
  std::vector<pii> stk;
  std::sort(vec.begin(), vec.end());
  for (auto p : vec) {
    for (; stk.size() >= 2 && mul(sub(p, stk.back()), sub(stk.back(), stk[stk.size() - 2])) <= 0; stk.pop_back()) {}
    stk.emplace_back(p);
  }
  return stk;
}

int decode(int x, int64_t lastans) {
  return x ^ (lastans & 0x7fffffff);
}

void build(int x) {
  std::sort(vec[x].begin(), vec[x].end());
  for (auto p : vec[x]) {
    for (; v[x][0].size() >= 2 && mul(sub(p, v[x][0].back()), sub(v[x][0].back(), v[x][0][v[x][0].size() - 2])) <= 0; v[x][0].pop_back()) {}
    v[x][0].emplace_back(p);
  }
  for (auto p : vec[x]) {
    for (; v[x][1].size() >= 2 && mul(sub(p, v[x][1].back()), sub(v[x][1].back(), v[x][1][v[x][1].size() - 2])) >= 0; v[x][1].pop_back()) {}
    v[x][1].emplace_back(p);
  }
}

int query(int x, pii p) {
  int o = (p.second <= 0), L = 0, R = v[x][o].size(), res = 0;
  while (L + 1 < R) {
    int mid = (L + R) >> 1;
    if (func(p, v[x][o][mid]) >= func(p, v[x][o][mid - 1])) L = res = mid;
    else R = mid;
  }
  return func(p, v[x][o][res]);
}

int query(int x, int l, int r, int ql, int qr, pii p) {
  if (l > qr || r < ql) return -1e18;
  else if (l >= ql && r <= qr) return query(x, p);
  int mid = (l + r) >> 1;
  return std::max(query(x << 1, l, mid, ql, qr, p), query(x << 1 | 1, mid + 1, r, ql, qr, p));
}

void dickdreamer() {
  std::cin >> q >> O;
  int lastans = 0;
  for (int cs = 1; cs <= q; ++cs) {
    std::string op;
    pii p;
    std::cin >> op >> p.first >> p.second;
    if (O[0] != 'E') p.first = decode(p.first, lastans), p.second = decode(p.second, lastans);
    if (op[0] == 'A') {
      for (int i = 0; i <= 19; ++i) {
        vec[getid(i, n >> i)].emplace_back(p);
        if ((n % (1 << i)) == (1 << i) - 1) build(getid(i, n >> i));
      }
      ++n;
    } else {
      int l, r;
      std::cin >> l >> r;
      if (O[0] != 'E') l = decode(l, lastans), r = decode(r, lastans);
      std::cout << (lastans = query(1, 0, (1 << 19) - 1, l - 1, r - 1, p)) << '\n';
    }
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2024-08-14 10:48  下蛋爷  阅读(43)  评论(0)    收藏  举报