二次离线莫队
算法提出
二次离线莫队是一个由 lxl
大佬提出的新科技,其基于莫队
+
+
+ 扫描线的思想,通过扫描线,再次将更新答案的过程离线处理,降低时间复杂度。
具体地,设更新答案的复杂度为 O ( k ) \mathcal O(k) O(k),那么它可以将莫队的复杂度从 O ( n k n ) \mathcal O(nk\sqrt n) O(nkn) 降到了 O ( n k + n n ) \mathcal O(nk+n\sqrt n) O(nk+nn), 大大简化了计算。
再补充一点,二次离线莫队通常适用于满足以下条件的题目:
- 可用莫队做。
- 莫队扩展或者删除一个点对答案的影响取决于当前区间的长度。
- 扩展或者删除一个点对答案的影响可用前缀写成差分的形式。
算法解决
以 模板题 为例。
给了你一个序列 a a a,每次查询给一个区间 [ l , r ] [l,r] [l,r],查询 l ≤ i < j ≤ r l \leq i< j \leq r l≤i<j≤r,且 a i ⊕ a j a_i \oplus a_j ai⊕aj 的二进制表示下有 k k k 个 1 1 1 的二元组 ( i , j ) (i,j) (i,j) 的个数。
1 ≤ n , m ≤ 1 0 5 , 0 ≤ a i , k < 16384 1 \leq n,m\leq10^5,0 \leq a_i,k < 16384 1≤n,m≤105,0≤ai,k<16384,时限 1 s 1 \text{s} 1s,空限 500 MB 500\text{MB} 500MB。
首先明确,这题如果用普通莫队的话,每一次移动指针的时候需要用 O ( C 14 k ) \mathcal O(C_{14}^k) O(C14k) 的复杂度来转移,显然过不去,于是考虑二次离线莫队。
设 a x a_x ax 对区间 [ l , r ] [l,r] [l,r] 的贡献为 f ( x , [ l , r ] ) f(x,[l,r]) f(x,[l,r])。
我们考虑区间端点变化对答案的影响,以 [ l , r ] [l,r] [l,r] 变成 [ l , r + k ] [l,r+k] [l,r+k] 为例,答案增加了 ∑ i = r + 1 r + k f ( i , [ l , i − 1 ] ) \sum\limits_{i=r+1}^{r+k}f(i,[l,i-1]) i=r+1∑r+kf(i,[l,i−1])。
我们发现, f ( i , [ l , i − 1 ] ) f(i,[l,i-1]) f(i,[l,i−1]) 可以差分成 f ( i , [ 1 , i − 1 ] ) − f ( i , [ 1 , l − 1 ] ) f(i,[1,i-1])-f(i,[1,l-1]) f(i,[1,i−1])−f(i,[1,l−1])。
这样转移区间的贡献就分为两类:
- 一个前缀和它后面一个数的贡献,这可以预处理出来。
- 区间 [ r + 1 , r + k ] [r+1,r+k] [r+1,r+k] 对前缀 [ 1 , l − 1 ] [1,l-1] [1,l−1] 的贡献,保存下来用扫描线做即可。
对于其他情况,大力讨论即可,下面给出结论:
- 右端点右移, [ l , r ] [l,r] [l,r] 变成 [ l , r + k ] [l,r+k] [l,r+k],答案增加了 ∑ i = r + 1 r + k f ( i , [ 1 , i − 1 ] ) − f ( i , [ 1 , l − 1 ] ) \sum\limits_{i=r+1}^{r+k}f(i,[1,i-1])-f(i,[1,l-1]) i=r+1∑r+kf(i,[1,i−1])−f(i,[1,l−1])。
- 右端点左移, [ l , r ] [l,r] [l,r] 变成 [ l , r − k ] [l,r-k] [l,r−k],答案减少了 ∑ i = r − k + 1 r f ( i , [ 1 , i − 1 ] ) − f ( i , [ 1 , l − 1 ] ) \sum\limits_{i=r-k+1}^{r}f(i,[1,i-1])-f(i,[1,l-1]) i=r−k+1∑rf(i,[1,i−1])−f(i,[1,l−1])。
- 左端点右移, [ l , r ] [l,r] [l,r] 变成 [ l + k , r ] [l+k,r] [l+k,r],答案减少了 ∑ i = l l + k − 1 f ( i , [ 1 , r ] ) − f ( i , [ 1 , i ] ) \sum\limits_{i=l}^{l+k-1}f(i,[1,r])-f(i,[1,i]) i=l∑l+k−1f(i,[1,r])−f(i,[1,i])。
- 左端点左移, [ l , r ] [l,r] [l,r] 变成 [ l − k , r ] [l-k,r] [l−k,r],答案增加了 ∑ i = l − k l − 1 f ( i , [ 1 , r ] ) − f ( i , [ 1 , i ] ) \sum\limits_{i=l-k}^{l-1}f(i,[1,r])-f(i,[1,i]) i=l−k∑l−1f(i,[1,r])−f(i,[1,i])。
再看扫描线部分。
我们对于每个前缀
[
1
,
i
]
[1,i]
[1,i] 开一个 vector
,其中装二元组
(
l
0
,
r
0
)
(l_0,r_0)
(l0,r0)(对应上面的
[
r
+
1
,
r
+
k
]
[r+1,r+k]
[r+1,r+k])(另外还要装一个
i
d
id
id,如果
i
d
<
0
id<0
id<0,代表减法,否则代表加法,贡献加到
∣
i
∣
\mid i \mid
∣i∣ 这里去)。
假设当前前缀为 [ 1 , i ] [1,i] [1,i],二元组为 ( l 0 , r 0 ) (l_0,r_0) (l0,r0),设 t [ z ] t[z] t[z] 表示当前前缀中的数异或 z z z 后得到的数恰好有 k k k 个 1 1 1 的数的个数。
再根据异或的一个性质:若 a ⊕ b = c a\oplus b=c a⊕b=c,则有 b ⊕ c = a , a ⊕ c = b b \oplus c=a,a \oplus c=b b⊕c=a,a⊕c=b。
所以插入一个 p p p 值时,就 O ( C 14 k ) \mathcal O(C_{14}^{k}) O(C14k) 枚举所有二进制表示下有 k k k 个 1 1 1 的数 v a l val val,然后令 t [ p ⊕ v a l ] + + t[p \oplus val]++ t[p⊕val]++ 即可(第一类贡献的预处理也用这种方法)。
询问时答案即为 ∑ i = l 0 r o t [ i ] \sum\limits_{i=l_0}^{r_o}t[i] i=l0∑rot[i]。
对于 f ( i , [ 1 , i ] ) f(i,[1,i]) f(i,[1,i]),由于 i ⊕ i = 0 i \oplus i=0 i⊕i=0 且 i = j i=j i=j 这个贡献不算,这可以转为 f ( i , [ 1 , i − 1 ] ) f(i,[1,i-1]) f(i,[1,i−1]),再加个特判即可。
由于第二类贡献需要先跑一次莫队记录 l 0 , r 0 l_0,r_0 l0,r0(顺便加上第一类贡献),然后再跑一次扫描线计算,相当于再次离线,所以我们通常称以上算法为二次离线莫队。
可以发现,莫队部分时间复杂度为 O ( n n ) \mathcal O(n\sqrt n) O(nn),当然也可以前缀和优化成 O ( n ) \mathcal O(n) O(n),扫描线部分时间复杂度为 O ( n n + n C 14 k ) \mathcal O(n\sqrt n+n C_{14}^k) O(nn+nC14k),所以总时间复杂度为 O ( n n + n C 14 k ) \mathcal O(n\sqrt n+n C_{14}^k) O(nn+nC14k),可过。
659ms / 15.43MB / 3.20KB C++20 O2 \text{659ms / 15.43MB / 3.20KB C++20 O2} 659ms / 15.43MB / 3.20KB C++20 O2。
#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 [Ynoi2019 模拟赛] Yuno loves sqrt technology II
给你一个长为 n n n 的序列 a a a, m m m 次询问,每次查询一个区间的逆序对数,不强制在线。
1 ≤ n , m ≤ 1 0 5 , 0 ≤ a i ≤ 1 0 9 1 \leq n,m\leq 10^5,0 \leq a_i \leq 10^9 1≤n,m≤105,0≤ai≤109,时限 250 ms 250\text{ms} 250ms,空限 31.25 MB 31.25\text{MB} 31.25MB。
首先明确,空限要求 O ( n ) \mathcal O(n) O(n)。
O ( n n log n ) \mathcal O(n \sqrt n \log n) O(nnlogn) 应该谁都会做,而且谁都知道被卡了。
考虑二次离线莫队。
前面的差分都不讲了。
对于第一类贡献,预处理即可。
对于第二类贡献,这里跟上面那题稍微有些不同,就是左端点移动的话答案变化量就是比这个数小的数的个数,右端点移动的话答案变化量就是比这个数大的数的个数。
由于第二类贡献如果用树状数组 O ( log n ) \mathcal O(\log n) O(logn) 修改, O ( log n ) \mathcal O(\log n) O(logn) 查询的话,时间复杂度是 O ( n log n + n n log n ) \mathcal O(n\log n+n \sqrt n \log n) O(nlogn+nnlogn) 的,过不去。
由于瓶颈在于查询,考虑值域分块维护前缀和, O ( n ) \mathcal O(\sqrt n) O(n) 修改, O ( 1 ) \mathcal O(1) O(1) 查询,这样时间复杂度是 O ( n n + n n ) \mathcal O(n \sqrt n+n\sqrt n) O(nn+nn),可过。
再加上莫队 O ( n n ) \mathcal O(n \sqrt n) O(nn) 的复杂度。
总时间复杂度为 O ( n n ) \mathcal O(n \sqrt n) O(nn),可过。
956ms / 19.04MB / 3.56KB C++11 O2 \text{ 956ms / 19.04MB / 3.56KB C++11 O2} 956ms / 19.04MB / 3.56KB C++11 O2。
#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 [LnOI2019]来者不拒,去者不追
给定一个长度为 n n n 的序列 a a a。给定 m m m 个询问,每次询问一个区间中 [ l , r ] [l,r] [l,r] 中所有数的
Abbi
值之和。
Abbi
值定义为:若 a i a_i ai 在询问区间 [ l , r ] [l,r] [l,r] 中是第 k k k 小,那么它的Abbi
值等于 k × a i k \times a_i k×ai。1 ≤ n , m ≤ 5 × 1 0 5 , 1 ≤ a i ≤ 1 0 5 1 \leq n,m\leq 5\times 10^5,1 \leq a_i \leq 10^5 1≤n,m≤5×105,1≤ai≤105,时限 1 s 1\text{s} 1s,空限 250 MB 250\text{MB} 250MB。
跟上面那题相似。
考虑 [ l , r ] [l,r] [l,r] 转移到 [ l , r + 1 ] [l,r+1] [l,r+1],即加入一个数的贡献,不难想,即为 ∑ i = l r [ a i < a r + 1 ] × a r + 1 + ∑ i = 1 r [ a i > a r + 1 ] a i + a 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} i=l∑r[ai<ar+1]×ar+1+i=1∑r[ai>ar+1]ai+ar+1。
向上面那题那样搞个值域分块维护一下即可。
细节:上面式子中最后加的那个 a r + 1 a_{r+1} ar+1 并不可以差分(手玩一下可以得知),需要最后加上。
具体参考代码。
7.87s / 88.66MB / 3.85KB C++17 O2 \text{7.87s / 88.66MB / 3.85KB C++17 O2} 7.87s / 88.66MB / 3.85KB C++17 O2。
#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 [Ynoi2018] GOSICK
给你一个序列 a a a,每次询问给一个区间 [ l , r ] [l,r] [l,r]。
查询 l ≤ i , j ≤ r l \leq i,j \leq r l≤i,j≤r 且 a i a_i ai 是 a j a_j aj 倍数的二元组 ( i , j ) (i,j) (i,j) 的个数。
1 ≤ n , m , a i ≤ 5 × 1 0 5 1 \leq n,m,a_i\leq 5\times 10^5 1≤n,m,ai≤5×105,时限 3 s 3\text{s} 3s,空限 128 MB 128\text{MB} 128MB。
sol
「点缀光辉的第十四分块」。
考虑二次离线莫队。
设 a i a_i ai 在区间 [ l , r ] [l,r] [l,r] 的因数个数为 f 1 ( i , [ l , r ] ) f_1(i,[l,r]) f1(i,[l,r]),倍数个数为 f 2 ( i , [ l , r ] ) f_2(i,[l,r]) f2(i,[l,r])。
则右端点向右移动加入一个数的贡献为 f 1 ( r , [ l , r ] ) + f 2 ( r , [ l , r ] ) f_1(r,[l,r])+f_2(r,[l,r]) f1(r,[l,r])+f2(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])-f_1(r,[1,l-1])+f_2(r,[1,r])-f_2(r,[1,l-1]) f1(r,[1,r])−f1(r,[1,l−1])+f2(r,[1,r])−f2(r,[1,l−1])。
f 1 ( r , [ 1 , r ] ) f_1(r,[1,r]) f1(r,[1,r]) 可以在遍历时 O ( n ) \mathcal O(\sqrt n) O(n) 枚举因数,累加所有因数之前的出现次数,定义一个桶即可实现。
f 2 ( r , [ 1 , r ] ) f_2(r,[1,r]) f2(r,[1,r]) 可以在上一个操作枚举因数时对该数的每个因数标记一下,即开一个桶记录该数作为因数的出现次数。
以上两部分可提前预处理,计算前缀和,然后跑一边莫队计算贡献,时间复杂度为 O ( n n ) \mathcal O(n\sqrt n) O(nn)。
对于 f 1 ( r , [ 1 , l − 1 ] ) f_1(r,[1,l-1]) f1(r,[1,l−1]) 和 f 2 r , [ 1 , l − 1 ] f_2{r,[1,l-1]} f2r,[1,l−1] 可在上面跑莫队时顺便存下来,进行二次离线,要求 O ( 1 ) \mathcal O(1) O(1) 查询 f 1 f_1 f1 和 f 2 f_2 f2。
考虑二次离线时的修改,这部分要求是 O ( n ) \mathcal O(\sqrt n) O(n) 级别的。
对于 f 1 f_1 f1,枚举因数,加上贡献即可。
对于 f 2 f_2 f2,考虑根号分治,设阈值 S S S:
- 若当前加入的数 ≥ S \geq S ≥S,暴力跳表,加上贡献。
- 若当前加入的数 < S < S <S,则将这个提出二次离线这部分,后面处理。
二次离线部分时间复杂度为 O ( n n ) \mathcal O(n \sqrt n) O(nn)。
再考虑刚刚遗留下来的一点东西。
记 p [ i ] [ j ] p[i][j] p[i][j] 表示区间 [ 1 , j ] [1,j] [1,j] 中含有因数 i i i 的数的个数, c [ i ] [ j ] c[i][j] c[i][j] 表示区间 [ 1 , j ] [1,j] [1,j] 中 i i i 的出现次数。
则区间 [ l , r ] [l,r] [l,r] 中含有因数 i i i 的数的个数就是 p [ i ] [ r ] − p [ i ] [ l − 1 ] p[i][r] - p[i][l-1] p[i][r]−p[i][l−1]。
所以 [ 1 , z ] [1,z] [1,z] 中的数 i i i 作为因数出现在 [ l , r ] [l,r] [l,r] 的总次数为 c [ i ] [ z ] × ( p [ i ] [ r ] − p [ i ] [ l − 1 ] ) c[i][z]\times (p[i][r] - p[i][l-1]) c[i][z]×(p[i][r]−p[i][l−1])。
那么枚举 i ∈ [ 1 , S ) i \in [1,S) i∈[1,S), O ( n ) \mathcal O(n) O(n) 求出 p p p 和 c c c(压掉一维),然后 O ( m ) \mathcal O(m) O(m) 计算贡献即可。
所以总时间复杂度为 O ( n n ) \mathcal O(n \sqrt n) O(nn),总空间复杂度为 O ( n ) \mathcal O(n) O(n)。
32.39s / 75.05MB / 3.92KB C++11 O2 \text{32.39s / 75.05MB / 3.92KB C++11 O2} 32.39s / 75.05MB / 3.92KB C++11 O2。
#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');
}