【模板】莫队二次离线

【模板】莫队二次离线

莫队二次离线是优化莫队的一种方法。

先从题目讲起,如试图 \(m\) 次询问一个区间求 \(\sum_{l\leq i< j\leq r}f(i, j)\),其中 \(f\) 是一个神秘但是有一点点性质的函数,然后用莫队算法就能将问题转化为 \(O(n\sqrt m)\) 次修改和查询。这里修改和查询是绑定的,如果修改和查询的复杂度比较差,假如是 \(O(k)\) 的,而又无法忍受 \(O(n\sqrt mk)\) 的算法,莫队二次离线就给了我们一个思路,可以尝试将问题变为 \(O(n)\) 次修改、\(O(n\sqrt m)\) 次查询,这样就有可能可以根号平衡。

大致思路就是,我们要求的实际上是 \(\sum_{l\leq i\leq r}f(l-1,i)\) 或者 \(\sum_{l\leq i\leq r}f(i,r+1)\) 这样的式子,由于是加法,我们考虑差分计算。记 \(F(x,p)=\sum_{1\leq i\leq p}f(x,i)\),那么前一个要求的式子就是 \(F(l-1,r)-F(l-1,l-1)\)。这样就有希望了,我们对着 \(p\) 做扫描线即可。另一边是对称的,写成倒过来的形式会好看一点。

但现在问题是,一共会有 \(O(n)\) 次修改、\(O(n\sqrt m)\) 次查询没错,但是如果将这 \(O(n\sqrt m)\) 次询问都存下来,可能无法忍受。考虑一个实际的情况,当前莫队区间是 \([l, r]\),要变成 \([ql,r]\)\(ql<l\)。那么我们直接将二元组 \(([ql,l-1],r)\) 记下来就好了,这样的二元组只有 \(O(m)\) 个,空间复杂度就对了。

为了代码写好看一点,可以将所有 \(F(x, x)\) 计算出来做前缀和。

模板题(P4887 【模板】莫队二次离线)代码如下:

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr int N = 1e5 + 10;
int n, m, k, a[N];
struct ask {
  int l, r, id;
} qrs[N];
struct node {
  int l, r, coe, id;
};
vector<node> buc[2][N];
LL ans[N], fans[N];
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  cin >> n >> m >> k;
  for (int i = 1; i <= n; i++) cin >> a[i];
  for (int i = 1; i <= m; i++) {
    int l, r;
    cin >> l >> r;
    qrs[i] = {l, r, i};
  }
  int B = max(1, (int)(n / sqrt(m)));
  sort(qrs + 1, qrs + m + 1, [&](auto lhs, auto rhs) {
    return lhs.l / B != rhs.l / B ? lhs.l < rhs.l : lhs.r < rhs.r;
  });
  for (int i = 1, l = 1, r = 0; i <= m; i++) {
    auto [ql, qr, id] = qrs[i];
    if (l > ql) buc[0][r].push_back({ql, l - 1, +1, i}), l = ql;
    if (r < qr) buc[1][l].push_back({r + 1, qr, +1, i}), r = qr;
    if (l < ql) buc[0][r].push_back({l, ql - 1, -1, i}), l = ql;
    if (r > qr) buc[1][l].push_back({qr + 1, r, -1, i}), r = qr;
  }
  vector<int> nums;
  for (int i = 0; i < 16384; i++) if (__builtin_popcount(i) == k) nums.push_back(i);
  static int cnt[16384];
  auto add = [&](int x) {
    for (int y : nums) cnt[a[x] ^ y] += 1;
  };
  auto qry = [&](int x) {
    return cnt[a[x]];
  };
  static LL pre[N];
  memset(cnt, 0, sizeof cnt);
  for (int i = 1; i <= n; i++) {
    add(i), pre[i] = pre[i - 1] + qry(i);
    for (auto [l, r, coe, id] : buc[0][i]) {
      ans[id] -= (pre[r] - pre[l - 1]) * coe;
      for (int x = l; x <= r; x++) ans[id] += coe * qry(x);
    }
  }
  memset(cnt, 0, sizeof cnt);
  for (int i = n; i >= 1; i--) {
    add(i), pre[i] = pre[i + 1] + qry(i);
    for (auto [l, r, coe, id] : buc[1][i]) {
      ans[id] -= (pre[l] - pre[r + 1]) * coe;
      for (int x = l; x <= r; x++) ans[id] += coe * qry(x);
    }
  }
  for (int i = 1; i <= m; i++) fans[qrs[i].id] = ans[i] += ans[i - 1];
  for (int i = 1; i <= m; i++) cout << fans[i] << endl;
  return 0;
}

posted @ 2025-06-14 21:22  caijianhong  阅读(45)  评论(0)    收藏  举报