[Ynoi2018] GOSICK
莫队二离好题。
Description
给定长度为 \(n\) 的序列 \(a\) 和 \(m\) 次询问,每次询问给定区间 \([l,\ r]\) ,求二元组 \((i,\ j)\) 满足 \(l \leq i,\ j \leq r\) 且 \(a_i\) 是 \(a_j\) 的倍数。
Analysis
这玩意似乎不太支持区间合并,或者说复杂度很不好看,考虑莫队能不能做。
又因为这个二元组仅针对两个数而言,可以差分,所以现在就要来分析怎么快速算 \([1,\ l]\) 和 \(r + 1\) , \([1,\ r]\) 和 \(r + 1\) 的答案。
相当于我们要维护 \(a_{r + 1}\) 在前缀中的倍数和因数数量
Solution
因数好说,因为每个数的因数个数是 根号级别的,均摊下来是能直接 \(O(\sqrt{n})\) 预处理的,之后就可以 \(O(1)\) 查询,在莫队里面就能接受了。但是倍数复杂度能构造成 \(O(n)\) 处理,然后被莽起干爆。
(然后我就又不会了)
复杂度瓶颈的话主要是因为数比较小的话倍数数量级很大,但是稍微大一点的就问题不大,所以考虑根号分治。
令阈值为 \(b\) , \(\geq b\) 的数直接暴力找倍数,因为倍数个数是 \(\frac{n}{b}\) 个的,时间复杂度 \(O(n \cdot \frac{n}{b})\) 。 \(< b\) 的数因为倍数数量太多,并且好像也没什么方法能优化时间,然后直接冲过去。
这时候就一定要避其锋芒。换个角度想想,我们想要的是在莫队里面能做到 \(O(1)\) 查询,但是这样子需要预处理,牺牲了很多时间,所以如果能不预处理还能 \(O(1)\) 查询就好了。
相当于现在对于一个节点 \(l\) 我们每次需要计算它的答案的时候用的是预处理的 \(ans\) ,所以我们想把所有 \(l\) 的询问一步到位。
考虑二次离线,把所有相同点的操作留下来堆一起,这样的话区间本质不同的查询只有 \(4 \cdot m\) 个,所有数一共也只有 \(b\) 个,可以强行判断每个数是不是询问点的倍数,时间复杂度是 \(O((n + m) \cdot b)\) 的。
这么说理论是 \(b\) 是取 \(\frac{n}{\sqrt{m}}\) 是最优的,但是实际上阈值往小一点取会更好。
Code
/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10, M = 1e7 + 10, B = 48;
int n, m, bel, a[N], mx, blo[N], L[N], R[N];
int dc[N], nm[M], cnt;
ll num[N], p1[N], p2[N], siz[N], ans[N], ret[N];
struct mdzz {
int l, r, id;
bool operator < (const mdzz &it) const {
return blo[l] == blo[it.l] ? r < it.r : l < it.l;
}
} p[N];
struct query {
int lt, rt, id, op, tim;
bool operator < (const query &it) const {
return tim < it.tim;
}
} q[N << 2];
const int Mxdt = 100000;
inline char gc() {
static char buf[Mxdt], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, Mxdt, stdin), p1 == p2) ? EOF : *p1++;
}
inline int read() {
char ch = gc();
int s = 0, w = 1;
while (!isdigit(ch)) {if (ch == '-') w = -1; ch = gc();}
while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = gc();}
return s * w;
}
int main() {
n = read(); m = read(); bel = sqrt(n);
for (int i = 1; i <= n; ++i) {
mx = max(mx, a[i] = read());
blo[i] = (i - 1) / bel + 1;
}
for (int i = 1; i <= m; ++i) p[i] = (mdzz) {read(), read(), i};
sort(p + 1, p + 1 + m);
for (int i = 1; i <= mx; ++i) {
for (int j = i; j <= mx; j += i) ++dc[j];
}
for (int i = 1; i <= mx; ++i) {
L[i] = R[i - 1] + 1;
R[i] = R[i - 1] + dc[i];
dc[i] = R[i - 1];
}
for (int i = 1; i <= mx; ++i) {
for (int j = i; j <= mx; j += i) nm[++dc[j]] = i;
}
for (int i = 1; i <= n; ++i) {
p1[i] += p1[i - 1];
for (int j = L[a[i]]; j <= R[a[i]]; ++j) ++num[nm[j]];
p1[i] += num[a[i]];
}
memset(num, 0, sizeof(num));
for (int i = 1; i <= n; ++i) {
p2[i] += p2[i - 1];
for (int j = L[a[i]]; j <= R[a[i]]; ++j) p2[i] += num[nm[j]];
++num[a[i]];
}
memset(num, 0, sizeof(num));
for (int i = 1, l = 1, r = 0, ql, qr; i <= m; ++i) {
ql = p[i].l; qr = p[i].r;
if (l > ql) {
q[++cnt] = (query) {ql, l - 1, i, 1, r};
ans[i] += p1[ql - 1] - p1[l - 1];
ans[i] += p2[ql - 1] - p2[l - 1];
l = ql;
}
if (l < ql) {
q[++cnt] = (query) {l, ql - 1, i, -1, r};
ans[i] += p1[ql - 1] - p1[l - 1];
ans[i] += p2[ql - 1] - p2[l - 1];
l = ql;
}
if (r < qr) {
q[++cnt] = (query) {r + 1, qr, i, -1, l - 1};
ans[i] += p1[qr] - p1[r];
ans[i] += p2[qr] - p2[r];
r = qr;
}
if (r > qr) {
q[++cnt] = (query) {qr + 1, r, i, 1, l - 1};
ans[i] += p1[qr] - p1[r];
ans[i] += p2[qr] - p2[r];
r = qr;
}
}
sort(q + 1, q + 1 + cnt);
int it = 0;
for (int i = 1, ql, qr; i <= cnt; ++i) {
while (it < q[i].tim) {
++it;
for (int j = L[a[it]]; j <= R[a[it]]; ++j) ++num[nm[j]];
if (a[it] > B) for (int j = a[it]; j <= mx; j += a[it]) ++num[j];
}
ql = q[i].lt; qr = q[i].rt;
for (int j = ql; j <= qr; ++j) ans[q[i].id] += q[i].op * num[a[j]];
}
for (int I = 1; I <= B; ++I) {
ll res = 0;
it = 0;
for (int i = 1; i <= n; ++i) siz[i] = siz[i - 1] + !(a[i] % I);
for (int i = 1, ql, qr; i <= cnt; ++i) {
while (it < q[i].tim) {
++it;
if (a[it] == I) ++res;
}
ql = q[i].lt; qr = q[i].rt;
ans[q[i].id] += q[i].op * (siz[qr] - siz[ql - 1]) * res;
}
}
for (int i = 1; i <= m; ++i) ret[p[i].id] = (ans[i] += ans[i - 1]);
for (int i = 1; i <= m; ++i) printf("%lld\n", ret[i]);
return 0;
}