莫队二次离线
前言
我可能在写得时候脑子不好会写漏一些东西,所以有不会的可以直接问我。
然后我的代码有点史,建议粘下来在本地食用。
简介
对于一类莫队题目,如果对答案的贡献可以差分,且修改无法快速修改,可以用莫队二次离线。
大概思想就是将贡献差分下来然后做扫描线,难在二次离线之后的数据结构应用?
做法
我们考虑每次莫队端点移动后产生的贡献。令 \(f(x,[l,r])\) 表示端点单次移动到 \(x\) 后对区间 \([l,r]\) 的贡献。可差分成 \(f(x,[1,r]) - f(x, [1,l-1])\)。我们让 \(f(x,p)\) 表示 \(x\) 对前缀 \([1,p]\) 的贡献。
-
当 \([l,r]\) 变为 \([l+p,r]\) 时,贡献减去 \(\sum_{i=l}^{l+p-1} f(i,r) - f(i,i)\)。
-
当 \([l,r]\) 变为 \([l-p,r]\) 时,贡献加上 \(\sum_{i=l-p}^{l-1} f(i,r) - f(i,i)\)。
-
当 \([l,r]\) 变为 \([l,r+p]\) 时,贡献加上 \(\sum_{i=r+1}^{r+p} f(i,i-1) - f(i,l-1)\)。
-
当 \([l,r]\) 变为 \([l,r-p]\) 时,贡献减去 \(\sum_{i=r-p+1}^{r} f(i,i-1) - f(i,l-1)\)。
这个 \(f(i,i)\) 和 \(f(i,i-1)\) 大多数情况时相同的,是好预处理的,做前缀和即可,令它为第一类贡献。剩下的东西相当于区间对前缀的贡献,令它为第二类贡献。
我们二次离线下来扫描线,每次遇到这个前缀就遍历产生贡献的区间处理答案。要求遍历区间里面处理答案要做到 \(O(1)\) (大多数情况)。
这个东西的复杂度是 \(O(n\sqrt m)\),具体地证明和普通莫队差不多,都是因为指针地移动次数是 \(n\sqrt m\) 的,这里不在赘述。
建议自己手动写一下方便理解,写代码的时候注意正负号的处理。
题目
例题1
注意到 \(a_i < 2^{14}\),所以 \(k > 14\) 输出 \(0\) 即可。我们考虑预处理有 \(k\) 个 \(1\) 的数的集合 \(S\)。那么 \(|S| < \binom{14}{k}\)。
根据异或的性质 \(a_i \oplus a_j = x\) 那么 \(a_i \oplus x = a_j\)。我们可以快速的处理第一类贡献。
第二类贡献里面每次暴力判就行了。\(k=0\) 相当于判两数相等,特殊处理即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
const int V = 16384;
struct Query {int l, r, id;
} q[N];
vector<Query> f[N];
int n, m, k, a[N], block, t[V << 1], sum[N], ans[N];
inline bool cmp(Query x, Query y) {return x.l / block == y.l / block ? (((x.l / block) & 1) ? x.r < y.r : x.r > y.r) : x.l < y.l;}
vector<int> d;
signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);cin >> n >> m >> k; block = n / sqrt(m); for (int i = 1; i <= n; ++i) cin >> a[i]; for (int i = 0; i < V; ++i) if (__builtin_popcount(i) == k) d.push_back(i);for (int i = 1; i <= m; ++i) cin >> q[i].l >> q[i].r , q[i].id = i; sort(q + 1, q + 1 + m, cmp);for (int i = 1; i <= n; ++i) {for (auto j : d) t[j ^ a[i]]++;sum[i] = sum[i - 1] + t[a[i + 1]];}memset(t, 0, sizeof t);for (int i = 1, l = 1, r = 0; i <= m; ++i) {int L = q[i].l, R = q[i].r;if (l > L) f[r].push_back({L, l - 1, q[i].id}), ans[q[i].id] += sum[L - 2] - sum[l - 2], l = L; if (r < R) f[l - 1].push_back({r + 1, R, -q[i].id}), ans[q[i].id] += sum[R - 1] - sum[r - 1], r = R;if (l < L) f[r].push_back({l, L - 1, -q[i].id}), ans[q[i].id] += sum[L - 2] - sum[l - 2], l = L;if (r > R) f[l - 1].push_back({R + 1, r, q[i].id}), ans[q[i].id] += sum[R - 1] - sum[r - 1], r = R;}for (int i = 1; i <= n; ++i) {for (auto j : d) ++t[a[i] ^ j];for (auto [l, r, id] : f[i]) {for (int j = l; j <= r; ++j) {int tmp = t[a[j]];if (j <= i && k == 0) --tmp;if (id < 0) ans[-id] -= tmp; else ans[id] += tmp;}}}for (int i = 1; i <= m; ++i)ans[q[i].id] += ans[q[i - 1].id];for (int i = 1; i <= m; ++i)cout << ans[i] << '\n';return 0;
}
例题2
我们分别考虑第一类贡献和第二类贡献怎么处理。对逆序对的贡献可以分成两种:
- 前面小于这个数的个数。
- 后面大于这个数的个数。
那么对于第一类贡献直接用树状数组预处理就行了。
第二类贡献我们要支持一个 \(O(1)\) 查排名的数据结构,不难想到值域分块,每次扫到一个数的时候更新块内的贡献和块外的贡献,修改复杂度 \(O(\sqrt n)\)。
注意因为前面和后面的数贡献不同,所以要维护前缀和和后缀和,\(f(x,p)\) 也要分成两种去处理。
然后本题有点卡常。
#include <bits/stdc++.h>
#define ll long long
#define rd read()
using namespace std;
const int N = 2e5 + 5;
struct Query {int l, r, id;
} q[N];
int n, m, a[N], B, block, tot, b[N];
ll pre[N], ans[N], suf[N], cnt[N], sum[N];
inline int read() {int x = 0;char ch = getchar();while (ch < '0' || ch > '9')ch = getchar();while (ch >= '0' && ch <= '9')x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();return x;
}
struct BIT {;int tr[N];inline void add(int x, int v) {++x; for (; x <= tot + 2; x += x & -x)tr[x] += v;}inline int ask(int x) {++x; int res = 0; for (; x; x -= x & -x)res += tr[x]; return res;}inline void clear() {memset(tr, 0, sizeof tr);}
} T;
inline bool cmp(Query x, Query y) {return x.l / B == y.l / B ? x.r < y.r : x.l < y.l;
}
vector<Query> f1[N], f2[N];
inline int ID(int x) {return (x - 1) / block + 1;}
inline int calc(int x) {return cnt[x] + sum[ID(x)];}
signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);n = rd, m = rd;B = max(1LL, (ll)(n / sqrt(m)));for (int i = 1; i <= n; ++i)b[i] = a[i] = rd;sort(b + 1, b + 1 + n);tot = unique(b + 1, b + 1 + n) - b - 1;block = max((ll)sqrt(tot), 1LL);for (int i = 1; i <= n; ++i)a[i] = lower_bound(b + 1, b + 1 + tot, a[i]) - b;for (int i = 1; i <= n; ++i) {pre[i] = pre[i - 1] + i - 1 - T.ask(a[i]);T.add(a[i], 1);}T.clear();for (int i = n; i; --i) {suf[i] = suf[i + 1] + T.ask(a[i] - 1); T.add(a[i], 1);}for (int i = 1; i <= m; ++i) {q[i].l = rd, q[i].r = rd; q[i].id = i;}sort(q + 1, q + 1 + m, cmp);for (int i = 1, l = 1, r = 0; i <= m; ++i) {int L = q[i].l, R = q[i].r;if (r > R)f1[l - 1].push_back({R + 1, r, q[i].id}), ans[q[i].id] -= pre[r] - pre[R], r = R;if (r < R)f1[l - 1].push_back({r + 1, R, -q[i].id}), ans[q[i].id] += pre[R] - pre[r], r = R;if (l < L)f2[r].push_back({l, L - 1, q[i].id}), ans[q[i].id] -= suf[l] - suf[L], l = L;if (l > L)f2[r].push_back({L, l - 1, -q[i].id}), ans[q[i].id] += suf[L] - suf[l], l = L;}for (int i = 1; i <= n; ++i) {for (int j = a[i] - 1; j >= (ID(a[i] - 1) - 1) * block + 1; --j)cnt[j]++;for (int j = ID(a[i] - 1) - 1; j; --j)++sum[j];for (auto [l, r, id] : f1[i]) {int tmp = 0;for (int j = l; j <= r; ++j) tmp += calc(a[j]);if (id < 0)ans[-id] -= tmp;else ans[id] += tmp;} }memset(cnt, 0, sizeof cnt), memset(sum, 0, sizeof sum);for (int i = n; i; --i) {for (auto [l, r, id] : f2[i]) {int tmp = 0;for (int j = l; j <= r; ++j) tmp += calc(a[j]);if (id < 0)ans[-id] -= tmp;else ans[id] += tmp;}for (int j = a[i] + 1; j <= ID(a[i] + 1) * block; ++j)++cnt[j];for (int j = ID(a[i] + 1) + 1; j <= ID(tot); ++j)++sum[j];}for (int i = 1; i <= m; ++i)ans[q[i].id] += ans[q[i - 1].id];for (int i = 1; i <= m; ++i)cout << ans[i] << '\n';return 0;
}