二次离线莫队学习笔记

· · 题解

二次离线莫队

算法提出

二次离线莫队是一个由 lxl 大佬提出的新科技,其基于莫队 + 扫描线的思想,通过扫描线,再次将更新答案的过程离线处理,降低时间复杂度。

具体地,设更新答案的复杂度为 \mathcal O(k),那么它可以将莫队的复杂度从 \mathcal O(nk\sqrt n) 降到了 \mathcal O(nk+n\sqrt n), 大大简化了计算。

再补充一点,二次离线莫队通常适用于满足以下条件的题目:

算法解决

以 模板题 为例。

给了你一个序列 a,每次查询给一个区间 [l,r],查询 l \leq i< j \leq r,且 a_i \oplus a_j 的二进制表示下有 k1 的二元组 (i,j) 的个数。

首先明确,这题如果用普通莫队的话,每一次移动指针的时候需要用 \mathcal O(C_{14}^k) 的复杂度来转移,显然过不去,于是考虑二次离线莫队。

a_x 对区间 [l,r] 的贡献为 f(x,[l,r])

我们考虑区间端点变化对答案的影响,以 [l,r] 变成 [l,r+k] 为例,答案增加了 \sum\limits_{i=r+1}^{r+k}f(i,[l,i-1])

我们发现,f(i,[l,i-1]) 可以差分成 f(i,[1,i-1])-f(i,[1,l-1])

这样转移区间的贡献就分为两类:

  1. 一个前缀和它后面一个数的贡献,这可以预处理出来。
  2. 区间 [r+1,r+k] 对前缀 [1,l-1] 的贡献,保存下来用扫描线做即可。

对于其他情况,大力讨论即可,下面给出结论:

再看扫描线部分。

我们对于每个前缀 [1,i] 开一个 vector,其中装二元组 (l_0,r_0)(对应上面的 [r+1,r+k])(另外还要装一个 id,如果 id<0,代表减法,否则代表加法,贡献加到 \mid i \mid 这里去)。

假设当前前缀为 [1,i],二元组为 (l_0,r_0),设 t[z] 表示当前前缀中的数异或 z 后得到的数恰好有 k1 的数的个数。

再根据异或的一个性质:若 a\oplus b=c,则有 b \oplus c=a,a \oplus c=b

所以插入一个 p 值时,就 \mathcal O(C_{14}^{k}) 枚举所有二进制表示下有 k1 的数 val,然后令 t[p \oplus val]++ 即可(第一类贡献的预处理也用这种方法)。

询问时答案即为 \sum\limits_{i=l_0}^{r_o}t[i]

对于 f(i,[1,i]),由于 i \oplus i=0i=j 这个贡献不算,这可以转为 f(i,[1,i-1]),再加个特判即可。

由于第二类贡献需要先跑一次莫队记录 l_0,r_0(顺便加上第一类贡献),然后再跑一次扫描线计算,相当于再次离线,所以我们通常称以上算法为二次离线莫队。

可以发现,莫队部分时间复杂度为 \mathcal O(n\sqrt n),当然也可以前缀和优化成 \mathcal O(n),扫描线部分时间复杂度为 \mathcal O(n\sqrt n+n C_{14}^k),所以总时间复杂度为 \mathcal O(n\sqrt n+n C_{14}^k),可过。

```cpp #include <bits/stdc++.h> using namespace std; #define re register namespace Fread { const int SIZE = 1 << 23; char buf[SIZE], *S, *T; inline char getchar() { if (S == T) { T = (S = buf) + fread(buf, 1, SIZE, stdin); if (S == T) return '\n'; } return *S++; } } namespace Fwrite { const int SIZE = 1 << 23; char buf[SIZE], *S = buf, *T = buf + SIZE; inline void flush() { fwrite(buf, 1, S - buf, stdout); S = buf; } inline void putchar(char c) { *S++ = c; if (S == T) flush(); } struct NTR { ~NTR() { flush(); } } ztr; } #ifdef ONLINE_JUDGE #define getchar Fread::getchar #define putchar Fwrite::putchar #endif typedef long long ll; inline int read() { re int x = 0, f = 1; re char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f; } inline void write(re ll x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const int _ = 1e5 + 7; int n, m, k, a[_], S, bel[_], t[_], pre[_]; ll Ans[_]; struct Query { int l, r, id; ll ans; inline bool operator < (const Query& tmp) { return bel[l] == bel[tmp.l] ? r < tmp.r : l < tmp.l; } } q[_]; vector<tuple<int, int, int>> v[_]; signed main() { n = read(), m = read(), k = read(); S = sqrt(n); if(k > 14) { for(re int i = 1; i <= m; ++i) putchar('0'), putchar('\n'); return 0; } for(re int i = 1; i <= n; ++i) a[i] = read(); for(re int i = 1; i <= m; ++i) { q[i].l = read(); q[i].r = read(); q[i].id = i; } vector<int> b; for(re int i = 0; i < 16384; ++i) if(__builtin_popcount(i) == k) b.push_back(i); for(re int i = 1; i <= n; ++i) bel[i] = (i - 1) / S + 1; sort(q + 1, q + m + 1); for(re int i = 1; i <= n; ++i) { for(re auto x : b) ++t[a[i] ^ x]; pre[i] = t[a[i + 1]]; } memset(t, 0, sizeof t); for(re int i = 1, l = 1, r = 0; i <= m; ++i) { if(l < q[i].l) v[r].emplace_back(l, q[i].l - 1, -i); while(l < q[i].l) { q[i].ans += pre[l - 1]; ++l; } if(l > q[i].l) v[r].emplace_back(q[i].l, l - 1, i); while(l > q[i].l) { q[i].ans -= pre[l - 2]; --l; } if(r < q[i].r) v[l - 1].emplace_back(r + 1, q[i].r, -i); while(r < q[i].r) { q[i].ans += pre[r]; ++r; } if(r > q[i].r) v[l - 1].emplace_back(q[i].r + 1, r, i); while(r > q[i].r) { q[i].ans -= pre[r - 1]; --r; } } for(re int i = 1, id, l, r; i <= n; ++i) { for(re auto x : b) ++t[a[i] ^ x]; for(re const auto& x : v[i]) { tie(l, r, id) = x; for(re int j = l, tmp = 0; j <= r; ++j) { tmp = t[a[j]]; if(j <= i && k == 0) --tmp; if(id < 0) q[-id].ans -= tmp; else q[id].ans += tmp; } } } for(re int i = 1; i <= m; ++i) q[i].ans += q[i - 1].ans; for(re int i = 1; i <= m; ++i) Ans[q[i].id] = q[i].ans; for(re int i = 1; i <= m; ++i) write(Ans[i]), putchar('\n'); } ``` ### 例题选讲 #### P5047 > 给你一个长为 $n$ 的序列 $a$,$m$ 次询问,每次查询一个区间的逆序对数,不强制在线。 > > $1 \leq n,m\leq 10^5,0 \leq a_i \leq 10^9$,时限 $250\text{ms}$,空限 $31.25\text{MB}$。 首先明确,空限要求 $\mathcal O(n)$。 $\mathcal O(n \sqrt n \log n)$ 应该谁都会做,而且谁都知道被卡了。 考虑二次离线莫队。 前面的差分都不讲了。 对于第一类贡献,预处理即可。 对于第二类贡献,这里跟上面那题稍微有些不同,就是左端点移动的话答案变化量就是比这个数小的数的个数,右端点移动的话答案变化量就是比这个数大的数的个数。 由于第二类贡献如果用树状数组 $\mathcal O(\log n)$ 修改,$\mathcal O(\log n)$ 查询的话,时间复杂度是 $\mathcal O(n\log n+n \sqrt n \log n)$ 的,过不去。 由于瓶颈在于查询,考虑值域分块维护前缀和,$\mathcal O(\sqrt n)$ 修改,$\mathcal O(1)$ 查询,这样时间复杂度是 $\mathcal O(n \sqrt n+n\sqrt n)$,可过。 再加上莫队$\mathcal O(n \sqrt n)$ 的复杂度。 总时间复杂度为 $\mathcal O(n \sqrt n)$,可过。 $\text{ 956ms / 19.04MB / 3.56KB C++11 O2}$。 ```cpp #include <bits/stdc++.h> using namespace std; #define re register namespace Fread { const int SIZE = 1 << 23; char buf[SIZE], *S, *T; inline char getchar() { if (S == T) { T = (S = buf) + fread(buf, 1, SIZE, stdin); if (S == T) return '\n'; } return *S++; } } namespace Fwrite { const int SIZE = 1 << 23; char buf[SIZE], *S = buf, *T = buf + SIZE; inline void flush() { fwrite(buf, 1, S - buf, stdout); S = buf; } inline void putchar(char c) { *S++ = c; if (S == T) flush(); } struct NTR { ~NTR() { flush(); } } ztr; } #ifdef ONLINE_JUDGE #define getchar Fread::getchar #define putchar Fwrite::putchar #endif typedef long long ll; inline int read() { re int x = 0, f = 1; re char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f; } inline void write(re ll x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const int _ = 1e5 + 7; int n, m, k, a[_], c[_], S, bel[_], t[_], pre[_], pree[_], f1[_], f2[_], L[_], R[_], V; ll Ans[_]; struct Query { int l, r, id; ll ans; inline bool operator < (const Query& tmp) { return bel[l] == bel[tmp.l] ? r < tmp.r : l < tmp.l; } } q[_]; vector<tuple<int, int, int, int>> v[_]; void init() { memset(pre, 0, sizeof pre); memset(pree, 0, sizeof pree); } void update(int x) { for(re int i = bel[x] + 1; i <= bel[V]; ++i) pre[i]++; for(re int i = x + 1; i <= R[bel[x]]; ++i) pree[i]++; } int query(int x) { return pre[bel[x]] + pree[x]; } signed main() { n = read(), m = read(); for(re int i = 1; i <= n; ++i) c[i] = a[i] = read(); sort(c + 1, c + n + 1); re int un = unique(c + 1, c + n + 1) - c - 1; for(int i = 1; i <= n; ++i) { a[i] = lower_bound(c + 1, c + un + 1, a[i]) - c; V = max(V, a[i]); } S = sqrt(V); for(re int i = 1; i <= m; ++i) { q[i].l = read(); q[i].r = read(); q[i].id = i; } for(re int i = 1; i <= V + 1; ++i) bel[i] = (i - 1) / S + 1; for(re int i = 1; i <= bel[V + 1]; ++i) L[i] = R[i - 1] + 1, R[i] = i * S; R[bel[V + 1]] = V + 1; sort(q + 1, q + m + 1); for(re int i = 1; i <= n; ++i) { f2[i] = i - query(a[i] + 1) - 1; f1[i] = query(a[i]); update(a[i]); } init(); for(re int i = 1, l = 1, r = 0; i <= m; ++i) { if(l < q[i].l) v[r].emplace_back(l, q[i].l - 1, -i, 0); while(l < q[i].l) q[i].ans += f1[l++]; if(l > q[i].l) v[r].emplace_back(q[i].l, l - 1, i, 0); while(l > q[i].l) q[i].ans -= f1[--l]; if(r < q[i].r) v[l - 1].emplace_back(r + 1, q[i].r, -i, 1); while(r < q[i].r) q[i].ans += f2[++r]; if(r > q[i].r) v[l - 1].emplace_back(q[i].r + 1, r, i, 1); while(r > q[i].r) q[i].ans -= f2[r--]; } for(re int i = 1, id, l, r, flg; i <= n; ++i) { update(a[i]); for(re const auto& x : v[i]) { tie(l, r, id, flg) = x; re ll tmp = 0; for(re int j = l; j <= r; ++j) { if(!flg) tmp = query(a[j]); else tmp = i - query(a[j] + 1); if(id < 0) q[-id].ans -= tmp; else q[id].ans += tmp; } } } for(re int i = 1; i <= m; ++i) q[i].ans += q[i - 1].ans; for(re int i = 1; i <= m; ++i) Ans[q[i].id] = q[i].ans; for(re int i = 1; i <= m; ++i) write(Ans[i]), putchar('\n'); } ``` ### P5501 > 给定一个长度为 $n$ 的序列 $a$。给定 $m$ 个询问,每次询问一个区间中 $[l,r]$ 中所有数的`Abbi` 值之和。 > > `Abbi`值定义为:若 $a_i$ 在询问区间 $[l,r]$ 中是第 $k$ 小,那么它的 `Abbi` 值等于 $k \times a_i$。 > > $1 \leq n,m\leq 5\times 10^5,1 \leq a_i \leq 10^5$,时限 $1\text{s}$,空限 $250\text{MB}$。 跟上面那题相似。 考虑 $[l,r]$ 转移到 $[l,r+1]$,即加入一个数的贡献,不难想,即为 $\sum\limits_{i=l}^{r}[a_i<a_{r+1}]\times a_{r+1}+\sum\limits_{i=1}^{r}[a_i>a_{r+1}]a_i+a_{r+1}$。 向上面那题那样搞个值域分块维护一下即可。 细节:上面式子中最后加的那个 $a_{r+1}$ 并不可以差分(手玩一下可以得知),需要最后加上。 具体参考代码。 $\text{7.87s / 88.66MB / 3.85KB C++17 O2}$。 ```cpp #include <bits/stdc++.h> using namespace std; #define re register typedef long long ll; namespace Fread { const int SIZE = 1 << 24; char buf[SIZE], *S, *T; inline char getchar() { if (S == T) { T = (S = buf) + fread(buf, 1, SIZE, stdin); if (S == T) return '\n'; } return *S++; } } namespace Fwrite { const int SIZE = 1 << 24; char buf[SIZE], *S = buf, *T = buf + SIZE; inline void flush() { fwrite(buf, 1, S - buf, stdout); S = buf; } inline void putchar(char c) { *S++ = c; if (S == T) flush(); } struct NTR { ~NTR() { flush(); } } ztr; } #ifdef ONLINE_JUDGE #define getchar Fread::getchar #define putchar Fwrite::putchar #endif inline int read() { re int x = 0, f = 1; re char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f; } inline void write(re ll x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const int _ = 5e5 + 7; int n, m, S, a[_], bel[_], pre1[_], pree1[_], L[_], R[_], V = 100000; ll pre[_], pree[_], s[_], F[_], Ans[_]; struct Query { int l, r, id; ll ans; inline bool operator < (re const Query& tmp) { return bel[l] == bel[tmp.l] ? r < tmp.r : l < tmp.l; } } q[_]; vector<tuple<int, int, int>> v[_]; struct tr { ll c[_]; void update(re int x, re int val) { for(int i = x; i <= V; i += i & -i) c[i] += val; } ll query(re int x) { ll res = 0; for(int i = x; i; i -= i & -i) res += c[i]; return res; } } t, t1; void init() { memset(pre, 0, sizeof pre); memset(pree, 0, sizeof pree); memset(pre1, 0, sizeof pre1); memset(pree1, 0, sizeof pree1); } void update(re int x) { for(re int i = bel[x]; i <= bel[V]; ++i) pre[i] += x, pre1[i]++; for(re int i = x; i <= R[bel[x]]; ++i) pree[i] += x, pree1[i]++; } ll query(re int x) { return pre[bel[x] - 1] + pree[x]; } int query1(re int x) { return pre1[bel[x] - 1] + pree1[x]; } signed main() { n = read(), m = read(); for(re int i = 1; i <= n; ++i) a[i] = read(), s[i] = s[i - 1] + a[i]; S = 317; for(re int i = 1; i <= m; ++i) { q[i].l = read(); q[i].r = read(); q[i].id = i; } for(re int i = 1; i <= V; ++i) bel[i] = (i - 1) / S + 1; for(re int i = 1; i <= bel[V]; ++i) L[i] = R[i - 1] + 1, R[i] = i * S; R[bel[V]] = V; sort(q + 1, q + m + 1); for(re int i = 1; i <= n; ++i) { F[i] = F[i - 1] + 1ll * t1.query(a[i] - 1) * a[i] + t.query(V) - t.query(a[i]); t.update(a[i], a[i]); t1.update(a[i], 1); } for(re int i = 1, l = 1, r = 0; i <= m; ++i) { if(l > q[i].l) v[r].emplace_back(q[i].l, l - 1, i), q[i].ans -= F[l - 1] - F[q[i].l - 1], l = q[i].l; if(r < q[i].r) v[l - 1].emplace_back(r + 1, q[i].r, -i), q[i].ans += F[q[i].r] - F[r], r = q[i].r; if(l < q[i].l) v[r].emplace_back(l, q[i].l - 1, -i), q[i].ans += F[q[i].l - 1] - F[l - 1], l = q[i].l; if(r > q[i].r) v[l - 1].emplace_back(q[i].r + 1, r, i), q[i].ans -= F[r] - F[q[i].r], r = q[i].r; } re ll sum = 0; for(re int i = 1, id, l, r; i <= n; ++i) { update(a[i]); sum += a[i]; for(re const auto& x : v[i]) { tie(l, r, id) = x; re ll tmp = 0; for(re int j = l; j <= r; ++j) { tmp = 1ll * query1(a[j] - 1) * a[j] + sum - query(a[j]); if(id < 0) q[-id].ans -= tmp; else q[id].ans += tmp; } } } for(re int i = 1; i <= m; ++i) q[i].ans += q[i - 1].ans; for(re int i = 1; i <= m; ++i) Ans[q[i].id] = q[i].ans + s[q[i].r] - s[q[i].l - 1]; for(re int i = 1; i <= m; ++i) write(Ans[i]), putchar('\n'); } ``` ### P5398 给你一个序列 $a$,每次询问给一个区间 $[l,r]$。 查询 $l \leq i,j \leq r$ 且 $a_i$ 是 $a_j$ 倍数的二元组 $(i,j)$ 的个数。 $1 \leq n,m,a_i\leq 5\times 10^5$,时限 $3\text{s}$,空限 $128\text{MB}$。 #### sol 「点缀光辉的第十四分块」。 考虑二次离线莫队。 设 $a_i$ 在区间 $[l,r]$ 的因数个数为 $f_1(i,[l,r])$,倍数个数为 $f_2(i,[l,r])$。 则右端点向右移动加入一个数的贡献为 $f_1(r,[l,r])+f_2(r,[l,r])$。 差分一下即为 $f_1(r,[1,r])-f_1(r,[1,l-1])+f_2(r,[1,r])-f_2(r,[1,l-1])$。 $f_1(r,[1,r])$ 可以在遍历时 $\mathcal O(\sqrt n)$ 枚举因数,累加所有因数之前的出现次数,定义一个桶即可实现。 $f_2(r,[1,r])$ 可以在上一个操作枚举因数时对该数的每个因数标记一下,即开一个桶记录该数作为因数的出现次数。 以上两部分可提前预处理,计算前缀和,然后跑一边莫队计算贡献,时间复杂度为 $\mathcal O(n\sqrt n)$。 对于 $f_1(r,[1,l-1])$ 和 $f_2{r,[1,l-1]}$ 可在上面跑莫队时顺便存下来,进行二次离线,要求 $\mathcal O(1)$ 查询 $f_1$ 和 $f_2$。 考虑二次离线时的修改,这部分要求是 $\mathcal O(\sqrt n)$ 级别的。 对于 $f_1$,枚举因数,加上贡献即可。 对于 $f_2$,考虑根号分治,设阈值 $S$: * 若当前加入的数 $\geq S$,暴力跳表,加上贡献。 * 若当前加入的数 $< S$,则将这个提出二次离线这部分,后面处理。 二次离线部分时间复杂度为 $\mathcal O(n \sqrt n)$。 再考虑刚刚遗留下来的一点东西。 记 $p[i][j]$ 表示区间 $[1,j]$ 中含有因数 $i$ 的数的个数,$c[i][j]$ 表示区间 $[1,j]$ 中 $i$ 的出现次数。 则区间 $[l,r]$ 中含有因数 $i$ 的数的个数就是 $p[i][r] - p[i][l-1]$。 所以 $[1,z]$ 中的数 $i$ 作为因数出现在 $[l,r]$ 的总次数为 $c[i][z]\times (p[i][r] - p[i][l-1])$。 那么枚举 $i \in [1,S)$,$\mathcal O(n)$ 求出 $p$ 和 $c$(压掉一维),然后 $\mathcal O(m)$ 计算贡献即可。 所以总时间复杂度为 $\mathcal O(n \sqrt n)$,总空间复杂度为 $\mathcal O(n)$。 $\text{32.39s / 75.05MB / 3.92KB C++11 O2}$。 ```cpp #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <vector> namespace Fread { const int SIZE = 1 << 23; char buf[SIZE], *S, *T; inline char getchar() { if (S == T) { T = (S = buf) + fread(buf, 1, SIZE, stdin); if (S == T) return '\n'; } return *S++; } } namespace Fwrite { const int SIZE = 1 << 23; char buf[SIZE], *S = buf, *T = buf + SIZE; inline void flush() { fwrite(buf, 1, S - buf, stdout); S = buf; } inline void putchar(char c) { *S++ = c; if (S == T) flush(); } struct NTR { ~NTR() { flush(); } } ztr; } #ifdef ONLINE_JUDGE #define getchar Fread::getchar #define putchar Fwrite::putchar #endif typedef long long ll; inline int read() { int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f; } inline void write(ll x) { if(x < 0) { putchar('-'); x = -x; } if(x > 9) write(x / 10); putchar(x % 10 + '0'); } const int _ = 5e5 + 7, S = 100; int n, m, k, a[_], bel[_], siz, mx, cnt[_], flg[_], pre[_]; ll f[_], Ans[_]; struct Query { int l, r, id; ll ans; inline bool operator < (const Query &t) const { return bel[l] == bel[t.l] ? (bel[l] & 1 ? r < t.r : r > t.r) : bel[l] < bel[t.l]; } } q[_]; struct Node { int l, r, id, x; bool operator < (const Node &t) const { return x < t.x; } } t[_ << 1]; std::vector<int> fac[_]; signed main() { n = read(), m = read(); siz = sqrt(n); for(int i = 1; i <= n; ++i) { a[i] = read(); bel[i] = (i - 1) / siz + 1; mx = std::max(mx, a[i]); if(fac[a[i]].empty()) { for(int j = 1; j * j <= a[i]; ++j) if(a[i] % j == 0) { f[i] += cnt[j]; flg[j]++; if(j * j != a[i]) { f[i] += cnt[a[i] / j]; flg[a[i] / j]++; } fac[a[i]].emplace_back(j); } } else { for(int x : fac[a[i]]) { f[i] += cnt[x]; flg[x]++; if(x * x != a[i]) { f[i] += cnt[a[i] / x]; flg[a[i] / x]++; } } } f[i] += f[i - 1] + flg[a[i]]; cnt[a[i]]++; } for(int i = 1; i <= m; ++i) q[i] = {read(), read(), i, 0}; std::sort(q + 1, q + m + 1); for(int i = 1, l = 1, r = 0; i <= m; ++i) { if(r < q[i].r) t[++k] = {r + 1, q[i].r, -i, l - 1}, q[i].ans += f[q[i].r] - f[r], r = q[i].r; if(l > q[i].l) t[++k] = {q[i].l, l - 1, i, r}, q[i].ans -= f[l - 1] - f[q[i].l - 1], l = q[i].l; if(r > q[i].r) t[++k] = {q[i].r + 1, r, i, l - 1}, q[i].ans -= f[r] - f[q[i].r], r = q[i].r; if(l < q[i].l) t[++k] = {l, q[i].l - 1, -i, r}, q[i].ans += f[q[i].l - 1] - f[l - 1], l = q[i].l; } std::sort(t + 1, t + k + 1); memset(f, 0, sizeof f); for(int i = 1, x = 0; i <= k; ++i) { while(x < t[i].x) { x++; for(int v : fac[a[x]]) { f[v]++; if(v * v != a[x]) f[a[x] / v]++; } if(a[x] > S) for(int j = 1; j * a[x] <= mx; ++j) f[j * a[x]]++; } for(int k = t[i].l; k <= t[i].r; ++k) if(t[i].id < 0) q[-t[i].id].ans -= f[a[k]]; else q[t[i].id].ans += f[a[k]]; } for(int i = 1; i <= S; ++i) { cnt[0] = pre[0] = 0; for(int j = 1; j <= n; ++j) { cnt[j] = cnt[j - 1] + (a[j] == i); pre[j] = pre[j - 1] + (a[j] % i == 0); } for(int j = 1; j <= k; ++j) if(t[j].id < 0) q[-t[j].id].ans -= (ll)cnt[t[j].x] * (ll)(pre[t[j].r] - pre[t[j].l - 1]); else q[t[j].id].ans += (ll)cnt[t[j].x] * (ll)(pre[t[j].r] - pre[t[j].l - 1]); } for(int i = 1; i <= m; ++i) { q[i].ans += q[i - 1].ans; Ans[q[i].id] = q[i].ans; } for(int i = 1; i <= m; ++i) write(Ans[i]), putchar('\n'); } ```