莫队
普通莫队
概念
普通莫队算法可以解决不强制要求在线的部分区间问题,主要是通过将询问按照最优顺序暴力处理来优化复杂度。
考虑 分块。将整个数组分成若干个长度都为 \(\sqrt{n}\) 的块,把这些询问按左端点所属块大的编号为第一关键字,以右端点为第二关键字升序排序。这样做的时间复杂度较优,为 \(O(n\sqrt{n})\) 。
莫队一般要求移动端点的转移复杂度较优,但是有两种情况可以优化:
-
其中一种操作比较简单,考虑回滚莫队。
-
题目满足可减性,考虑莫队二离。
例题
这种套路通常都需要配合 离散化 和 桶 来使用。
直接用桶讨论一下端点的转移就行。
注意 \(l, r\) 的初始值分别赋 \(1, 0\).
带修莫队
概念
考虑将修改一起离线。
在普通的莫队算法上增加一个指针 \(k\) ,表示当前的区间是经过了前 \(k\) 次修改的情况,\(k\) 的初始值为 \(0\) 。
处理询问的时候连带修改的指针一起移动就行。
优化
直接上手难优化,考虑传统艺能调块长。
假设我们取块长等于 \(n^x\) ,其中 \(x\) 满足 \(0 < x < 1\),且操作次数 \(m\) 与 \(n\) 同阶。
考虑当 \(l\) 指针移动时,最坏的单次时间复杂度:
-
当 \(l, r\) 指针全部都在块内移动时:
最坏要遍历一个整块,单次时间复杂度为 \(O(n^x)\) 。一共有 \(O(n)\) 次询问,总时间复杂度为 \(O(n^{x + 1})\) 。
-
当 \(l\) 指针移动到后面的若干块时:
每移动一个整块的时间复杂度为 \(O(n^x)\) 。可以作为终点的块共有 \(O(\frac{n}{n^x})\) 个,所以时间复杂度最坏为 \(O(n)\) 。
所以,\(l\) 指针移动的最坏时间复杂度为 \(O(x + 1)\) 。
接下来考虑移动 \(r\) 指针的最坏复杂度,我们叠加上 \(l\) 指针的影响,不会干扰到总时间复杂度的分析:
-
当 \(l\) 和 \(r\) 指针都在块内移动时:
两个指针可以移动的位置分别有 \(O(n^x)\) 个。一共有 \(\frac{n}{n^x}\) 个块,所以总时间复杂度为 \(O(n^{2x} \times \frac{n}{n^x})\) ,即 \(O(n^{2 - x})\) 。
-
当 \(l\) 指针在块内移动,\(r\) 指针移动到后面的若干块时:
\(r\) 指针每移动一个整块的单次时间复杂度为 \(O(n^x)\)。最坏情况下需要遍历 \(\frac{n}{n^x}\) 个块。
由于右端点按照升序排列,假设每一个块需要付出 \(O(n)\) 的时间复杂度,则最坏复杂度接近于 \(O(\frac{n}{n^x} \times n)\),化简得 \(O(n^{2 - x})\) 。
-
当 \(l\) 指针移动到下一块时:
最坏单次需要 \(O(n)\) 的时间复杂度。向大估算,每个块都需要左指针遍历一次整个区间。共有 \(O(\frac{n}{n^x })\) 个块,每次需要 \(O(n)\) 遍历一次整个区间,则最坏复杂度接近于 \(O(n^{2 - x})\) 。
所以\(r\) 指针移动得时间复杂度最坏也接近于 \(O(n^{2 - x})\) 。
最后考虑 \(k\) 指针移动的时间复杂度:
-
\(l, r\) 指针都在块内移动时:
\(k\) 指针按照排序规则递增,最坏时间复杂度为 \(O(n)\) 。
-
\(l\) 指针在块内移动时:
\(r\) 指针移动到后面的若干块时,每一次移动的复杂度最坏也是 \(O(n)\)。
根据上面的估算,每次移动 \(l, r\) 指针最坏需要 \(O(n^{2 - 2x})\) ,每次移动右指针的同时还要最坏 \(O(n)\) 地更新 \(k\) 指针,所以总时间复杂度为 \(O(n^{3 - 2x})\) 。
-
\(l\) 指针移动到后面的若干块时:
每次最坏时间复杂度为 \(O(n)\) 。假设每个块贡献一次移动的复杂度,则最坏复杂度不超过 \(O(n^{1 - x})\) ,考虑进 \(k\) 指针的影响,总时间复杂度不超过 \(O(n^{2 - x})\) 。
所以 \(k\) 指针移动的总时间复杂度不超过 \(O(n^{\max(3 - 2x, 2 - x)})\)
三个指针移动的时间复杂度最坏为 \(O(n^{max(1, 3 - 2x, 2 - x)})\) ,进行数学分析后易知 \(x = \frac{2}{3}\) 时时间复杂度最优,为 \(O(n^{\frac{5}{3}})\) 。
带修莫队还有另外一种块长 \(= \sqrt[3]{nt}\) 的写法,理论上可以达到理论最优复杂度 \(O(\sqrt[3]{n^4t})\) 。
例题
典题。
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5e4;
const int maxq = 1e5 + 5e4;
const int maxv = 1e6 + 5;
int n, m, cur;
int l, r, k;
int qlen, clen;
int a[maxn], bel[maxn];
int cnt[maxv], ans[maxq];
struct ques
{
int l, r, k, id;
bool operator < (const ques& rhs) const
{
if (bel[l] ^ bel[rhs.l])
return bel[l] < bel[rhs.l];
if (bel[r] ^ bel[rhs.r])
return (bel[l] & 1 ? r < rhs.r : r > rhs.r);
return k < rhs.k;
}
} q[maxq];
struct node
{
int pos, color;
} c[maxq];
inline int read()
{
int res = 0, flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
flag = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res * flag;
}
inline void write(int x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
inline void add(int x)
{
if (!cnt[a[x]])
cur++;
cnt[a[x]]++;
}
inline void del(int x)
{
cnt[a[x]]--;
if (!cnt[a[x]])
cur--;
}
inline void update(int x)
{
if (c[x].pos >= l && c[x].pos <= r)
{
if (!cnt[c[x].color])
cur++;
if (cnt[a[c[x].pos]] == 1)
cur--;
cnt[c[x].color]++;
cnt[a[c[x].pos]]--;
}
swap(c[x].color, a[c[x].pos]);
}
int main()
{
char ch;
n = read();
m = read();
int block = pow(n, 0.666);
for (register int i = 1; i <= n; i++)
{
a[i] = read();
bel[i] = (i - 1) / block + 1;
}
for (register int i = 1; i <= m; i++)
{
scanf(" %c ", &ch);
if (ch == 'Q')
{
qlen++;
q[qlen].l = read();
q[qlen].r = read();
q[qlen].id = qlen;
q[qlen].k = clen;
}
else
{
clen++;
c[clen].pos = read();
c[clen].color = read();
}
}
sort(q + 1, q + qlen + 1);
for (register int i = 1; i <= qlen; i++)
{
while (l < q[i].l)
del(l++);
while (l > q[i].l)
add(--l);
while (r < q[i].r)
add(++r);
while (r > q[i].r)
del(r--);
while (k < q[i].k)
update(++k);
while (k > q[i].k)
update(k--);
ans[q[i].id] = cur;
}
for (register int i = 1; i <= qlen; i++)
write(ans[i]), puts("");
return 0;
}
回滚莫队
概念
假设有一种莫队,它的修改或者删除操作中有一个很难实现,另一个比较简单。
可以考虑使用回滚莫队维护。同样考虑暴力撤销,再通过合理排序来优化复杂度。
首先把询问按照左端点所属块的编号为第一关键字,以右端点为第二关键字升序排序。
把询问按照左端点所属块的编号分类。
对于同一类的询问,我们可以将 \(r\) 指针初始化为这个块的结尾,\(l\) 指针初始化为下一个块的开头。
每次对于当前询问,如果 \(r\) 指针在当前询问右端点的左端,我们就把右端点右移。
由于这些询问的左端点都在同一个块内,所以它们是按右端点升序排列的,因而可以沿用之前的 \(r\) 指针更新的信息,也就是这个块的末尾以后的信息。
完成更新以后保存答案,设其为 \(k\) 。
特殊情况是某个询问的左端点和右端点在同一个块内,此时暴力做法的时间复杂度不超过 \(O(\sqrt{n})\) ,直接暴力即可。
如果 \(l\) 指针在当前询问的左端点的右端,那么我们把 \(l\) 指针左移到当前询问的左端点并更新答案。
此时这个询问的答案已经被求出。我们还需要把左端点的影响删除,并且把左端点还原到下一个块的开头。
每次对于在同一个块内的询问重复以上操作,注意清除左指针带来的影响即可。总的时间复杂度不超过 \(O(n\sqrt{n} + m\sqrt{n})\)。
证明
证明如下:我们把所有询问分成两类,设左右端点在同一块内的询问数量为 \(a\) ,左右端点不在同一块内的询问数量为 \(b\),所有询问的总数量为 \(m\)。
对于第一类询问,左右指针每次最多扫描 \(O(\sqrt{n})\) 个位置,所以处理第一类询问的总时间复杂度为 \(O(a\sqrt{n})\) 。
对于第二类询问,我们再次分类,设左端点在第 \(i\) 个块内的询问为第 \(i\) 类询问。显然,对于每一个询问左指针最多扫描 \(\sqrt{n}\) 个位置。
由于每个块内的右端点按升序排列,每一类询问右指针最多扫描 \(n\) 个位置。最多有 \(\sqrt{n}\) 类询问,左指针需要扫描的总长度最长为 \(b \times \sqrt{n}\) ,右指针需要扫描的总长度最长为 \(\sqrt{n} \times n\) 。求和可知最长需要扫描 \(b \times \sqrt{n} + n \times \sqrt{n}\) 个位置。
两类询问求和后为 \(a\sqrt{n} + b\sqrt{n} + n\sqrt{n}\),整理可得总时间复杂度为 \(O(n\sqrt{n} + m\sqrt{n})\) 。
例题
典题,难点在于值域太大。
我们发现处理出现次数时只需要数的相对大小,也就是维护第 \(k\) 大数的出现次数。
所以可以考虑莫队时使用离散化后的数组,求值时再调用原本数组里的值。
离散化后维护每一个值的出现次数,出现次数增加时顺带修改答案即可。详见代码。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
const int maxq = 1e5 + 5;
const int maxv = 1e5 + 5;
const int maxb = 1e3 + 5;
int n, m;
int l, r;
int st[maxb], ed[maxb];
int bel[maxn], cnt[maxv];
int a[maxn], b[maxn], c[maxn];
long long vis, cur;
long long ans[maxq];
struct ques
{
int l, r, id;
bool operator < (const ques& rhs) const
{
if (bel[l] ^ bel[rhs.l])
return bel[l] < bel[rhs.l];
return r < rhs.r;
}
} q[maxq];
inline int read()
{
int res = 0, flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
flag = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res * flag;
}
inline void write(long long x)
{
if (x < 0)
{
x = -x;
putchar('-');
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
inline void add(int x)
{
cnt[a[x]]++;
cur = max(cur, (long long)cnt[a[x]] * b[a[x]]);
}
inline long long solve(int l, int r)
{
long long res = 0;
for (register int i = l; i <= r; i++)
c[a[i]] = 0;
for (register int i = l; i <= r; i++)
{
c[a[i]]++;
res = max(res, (long long)c[a[i]] * b[a[i]]);
}
return res;
}
int main()
{
int idx = 1;
n = read();
m = read();
int block = sqrt(n);
int size = ceil(n * 1.0 / block);
for (register int i = 1; i <= size; i++)
{
st[i] = (i - 1) * block + 1;
ed[i] = i * block;
}
for (register int i = 1; i <= n; i++)
{
a[i] = b[i] = read();
bel[i] = (i - 1) / block + 1;
}
for (register int i = 1; i <= m; i++)
{
q[i].l = read();
q[i].r = read();
q[i].id = i;
}
sort(q + 1, q + m + 1);
sort(b + 1, b + n + 1);
int len = unique(b + 1, b + n + 1) - b - 1;
for (register int i = 1; i <= n; i++)
a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
for (register int i = 1; i <= size; i++)
{
memset(cnt, 0, (n + 1) * sizeof(int));
cur = 0;
r = ed[i];
while (bel[q[idx].l] == i)
{
l = ed[i] + 1;
if (bel[q[idx].l] == bel[q[idx].r])
{
ans[q[idx].id] = solve(q[idx].l, q[idx].r);
idx++;
continue;
}
while (r < q[idx].r)
add(++r);
vis = cur;
while (l > q[idx].l)
add(--l);
ans[q[idx].id] = cur;
cur = vis;
while (l <= ed[i])
cnt[a[l++]]--;
idx++;
}
}
for (register int i = 1; i <= m; i++)
write(ans[i]), putchar('\n');
return 0;
}
树上莫队
概念
考虑用 dfs 序或者欧拉序拍平成序列问题做。
例题
SP10707 COT2 - Count on a tree II
考虑欧拉序拍平。
设结点 \(i\) 在欧拉序中第一次出现的位置为 \(s_i\) ,最后一次出现的位置为 \(t_i\)。
假设有两个结点 \(x, y\) 满足 \(s_x < s_y\) ,此时若 \(x, y\) 的 \(lca = x\) ,说明 \(y\) 是 \(x\) 的后代。
对于欧拉序中的区间 \([s_x, s_y]\) ,其中出现且仅出现 \(1\) 次的结点一定在 \(x\) 到 \(y\) 的路径上。
如果 \(lca(x, y) \neq x\) ,说明 \(x\) 和 \(y\) 在两棵不同的子树内,此时取区间 \(t_x, s_y\) 。
对于第二种情况,可能会缺掉 lca。在维护莫队的时候还需要更新 \(x, y\) 的 \(lca\) 。
注意树上莫队因为对欧拉序进行处理,所以 bel
数组要处理 \(2n\) 个位置。
代码
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 4e4 + 5;
const int maxm = 1e5 + 5;
int n, m;
int tot, Cnt, cur;
int f[maxn][20], st[maxn], ed[maxn];
int cnt[maxn], ans[maxm], p[maxn * 2];
int head[maxn], dep[maxn], a[maxn], b[maxn], bel[maxn * 2];
bool vis[maxn * 2];
struct edge
{
int to, nxt;
} edge[2 * maxn];
struct ques
{
int l, r, lca, id;
bool operator < (const ques& rhs) const
{
if (bel[l] ^ bel[rhs.l])
return bel[l] < bel[rhs.l];
return (bel[l] & 1 ? r < rhs.r : r > rhs.r);
}
} q[maxm];
inline int read()
{
int res = 0, flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
flag = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res * flag;
}
void add_edge(int u, int v)
{
Cnt++;
edge[Cnt].to = v;
edge[Cnt].nxt = head[u];
head[u] = Cnt;
}
void dfs(int u)
{
p[++tot] = u;
st[u] = tot;
dep[u] = dep[f[u][0]] + 1;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (v != f[u][0])
{
f[v][0] = u;
dfs(v);
}
}
p[++tot] = u;
ed[u] = tot;
}
void add(int x)
{
if (!vis[x])
{
if (!cnt[a[x]])
cur++;
cnt[a[x]]++;
}
else
{
cnt[a[x]]--;
if (!cnt[a[x]])
cur--;
}
vis[x] ^= 1;
}
int lca(int u, int v)
{
if (dep[u] < dep[v])
swap(u, v);
int k = 0;
while ((1 << (k + 1)) <= n)
k++;
for (int i = k; i >= 0; i--)
if (dep[f[u][i]] >= dep[v])
u = f[u][i];
if (u == v)
return u;
for (int i = k; i >= 0; i--)
{
if (f[u][i] != f[v][i])
{
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
int main()
{
int u, v, x;
int l = 1, r = 0;
n = read();
m = read();
for (int i = 1; i <= n; i++)
a[i] = b[i] = read();
for (int i = 1; i <= n - 1; i++)
{
u = read();
v = read();
add_edge(u, v);
add_edge(v, u);
}
sort(b + 1, b + n + 1);
int len = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++)
a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
int block = sqrt(n);
for (int i = 1; i <= 2 * n; i++)
bel[i] = (i - 1) / block + 1;
dfs(1);
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i <= n; i++)
f[i][j] = f[f[i][j - 1]][j - 1];
for (int i = 1; i <= m; i++)
{
u = read();
v = read();
if (st[u] >= st[v])
swap(u, v);
x = lca(u, v);
q[i].id = i;
if (x == u)
{
q[i].l = st[u];
q[i].r = st[v];
}
else
{
q[i].l = ed[u];
q[i].r = st[v];
q[i].lca = x;
}
}
sort(q + 1, q + m + 1);
for (int i = 1; i <= m; i++)
{
while (l < q[i].l)
add(p[l++]);
while (l > q[i].l)
add(p[--l]);
while (r < q[i].r)
add(p[++r]);
while (r > q[i].r)
add(p[r--]);
if (q[i].lca)
add(q[i].lca);
ans[q[i].id] = cur;
if (q[i].lca)
add(q[i].lca);
}
for (int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
return 0;
}
优化
奇偶性优化
若左端点所属的块不同,我们按左端点所属块的编号排序,否则若左端点所属块的编号为奇数,我们按右端点升序排序,否则按右端点降序排序。理论最多可以优化 \(\frac{1}{2}\) 的常数。
bool operator < (const ques& rhs) const
{
if (bel[l] ^ bel[rhs.l])
return bel[l] < bel[rhs.l];
return (bel[l] & 1 ? r < rhs.r : r > rhs.r);
}
莫队二离
莫队二次离线,用于移动端点难以 \(O(1)\) 转移贡献的情况。
考虑对莫队的移动操作进行再次离线维护,从而简化问题。
要求维护的信息满足可加减性。
例题:P4887 【模板】莫队二次离线(第十四分块(前体))
令 \(f(x, [l, r])\) 为位置 \(x\) 对区间 \([l, r]\) 的贡献。
考虑分讨一下端点移动的情况:
-
左端点左移,\([l, r]\) -> \([l - k, r]\),贡献增加 \(\sum\limits_{i = l - k}^{l - 1} f(i, [1, r]) - f(i, [1, i - 1])\)
-
左端点右移,\([l, r]\) -> \([l + k, r]\),贡献减少 \(\sum\limits_{i = l}^{l + k - 1} f(i, [1, r]) - f(i, [1, i - 1])\)
-
右端点左移,\([l, r]\) -> \([l, r - k]\),贡献减少 \(\sum\limits_{i = r - k + 1}^{r} f(i, [1, i - 1]) - f(i, [1, l - 1])\)
-
右端点右移,\([l, r]\) -> \([l, r + k]\),贡献增加 \(\sum\limits_{i = r + 1}^{r + k} f(i, [1, i - 1]) - f(i, [1, l - 1])\)
不妨以最后一种情况为例,考虑两边的式子如何维护。
首先左边的 \(f(i, [1, i - 1])\) 可以预处理。
对于右边的 \(f(i, [1, l - 1])\) 实际上是询问在某个前缀位置与 \(i\) 产生的贡献总和,可以考虑从前向后扫描线维护。
也就是在 \(l - 1\) 位置挂上一个对 \([r + 1, r + k]\) 区间的询问,求该区间内与 \([1, l - 1]\) 产生的贡献总和。这里因为 \(k \leq \sqrt{n}\),所以可以直接暴力扫描区间。
具体到这个题直接开桶计数就好了。
时间复杂度 \(O({n \choose 17}n + n \sqrt{m})\)
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
namespace IO
{
//by cyffff
int len = 0;
char ibuf[(1 << 20) + 1], *iS, *iT, out[(1 << 26) + 1];
#define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
#define reg register
inline int read()
{
reg char ch = gh();
reg int x = 0;
reg char t = 0;
while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
while (ch >= '0' && ch <= '9') x = x * 10 + (ch ^ 48), ch = gh();
return t ? -x : x;
}
inline void putc(char ch) { out[len++] = ch; }
template<class T>
inline void write(T x)
{
if (x < 0) putc('-'), x = -x;
if (x > 9) write(x / 10);
out[len++] = x % 10 + 48;
}
inline void flush()
{
fwrite(out, 1, len, stdout);
len = 0;
}
}
using IO::read;
using IO::write;
using IO::flush;
using IO::putc;
const int maxn = 1e5 + 5;
const int maxm = 1e5 + 5;
const int maxv = (1 << 14);
int n, m, k;
int buc[maxv];
int a[maxn], bel[maxn], pre[maxn];
ll ans[maxm];
vector<int> num;
struct Query
{
int l, r, idx;
ll ans;
Query() : l(), r(), idx(), ans() {}
Query(int _l, int _r, int _idx) : l(_l), r(_r), idx(_idx), ans() {}
bool operator < (const Query& rhs) const { return (bel[l] == bel[rhs.l] ? r < rhs.r : l < rhs.l); }
} qry[maxn];
vector<Query> opt[maxn];
int main()
{
// freopen("P4887_13.in", "r", stdin);
// freopen("P4887_13.res", "w", stdout);
n = read(), m = read(), k = read();
if (k > 14)
{
while (m--) puts("0");
return 0;
}
int blk = sqrt(n);
for (int i = 1; i <= n; i++) bel[i] = (i - 1) / blk + 1, a[i] = read();
for (int i = 1; i <= m; i++) qry[i].idx = i, qry[i].l = read(), qry[i].r = read();
sort(qry + 1, qry + m + 1);
for (int i = 0; i < maxv; i++)
if (__builtin_popcount(i) == k) num.push_back(i);
for (int i = 1; i <= n; i++)
{
for (int v : num) buc[a[i] ^ v]++;
pre[i] = buc[a[i + 1]];
}
for (int i = 1, l = 1, r = 0; i <= m; i++)
{
if (l < qry[i].l) opt[r].push_back(Query(l, qry[i].l - 1, -i));
while (l < qry[i].l) qry[i].ans += pre[l - 1], l++;
if (l > qry[i].l) opt[r].push_back(Query(qry[i].l, l - 1, i));
while (l > qry[i].l) qry[i].ans -= pre[l - 2], l--;
if (r < qry[i].r) opt[l - 1].push_back(Query(r + 1, qry[i].r, -i));
while (r < qry[i].r) qry[i].ans += pre[r], r++;
if (r > qry[i].r) opt[l - 1].push_back(Query(qry[i].r + 1, r, i));
while (r > qry[i].r) qry[i].ans -= pre[r - 1], r--;
}
memset(buc, 0, sizeof(buc));
for (int i = 1; i <= n; i++)
{
for (int v : num) buc[a[i] ^ v]++;
for (Query x : opt[i])
{
for (int j = x.l, tmp; j <= x.r; j++)
{
tmp = buc[a[j]];
if ((j <= i) && (k == 0)) tmp--;
if (x.idx < 0) qry[-x.idx].ans -= tmp;
else qry[x.idx].ans += tmp;
}
}
}
for (int i = 1; i <= m; i++) qry[i].ans += qry[i - 1].ans, ans[qry[i].idx] = qry[i].ans;
for (int i = 1; i <= m; i++) write(ans[i]), putc('\n');
flush();
return 0;
}
来自 lxl 的尖端莫队科技。
如果想将莫队的复杂度优化至 \(\mathcal{O}(n \sqrt{q})\)
您只需将块长改成 \(\frac{n}{\sqrt{q}}\)