分块与根号算法入门
update:
- 2024.4.13:完工,修改与整理
- 2025.3.4:修 markdown,移除一些大分块至分块进阶,加入树分块
- 2025.3.5:加入莫队二离
0. 根号算法
一些无法以 \(\mathrm{polylog}\) 复杂度实现的题,又不能暴力通过,这时根号算法就是一个不错的选择。
wcnm 联合省选 2025 D1T2。
1. 整除分块
这一部分较为简单。
1.1 概念与解法
整除分块是要求形如 \(\displaystyle\sum_{i=1}^{n} f(i)\left\lfloor\dfrac{n}{i}\right\rfloor\) 的式子,我们要求 \(f(n)\) 的前缀和是易求的。
暴力是 \(\mathcal{O}(n)\) 的,不太好。
我们考虑把 \(\left\lfloor\dfrac{n}{i}\right\rfloor\) 相同的 \(i\) 一起计算,可以发现 \(\left\lfloor\dfrac{n}{i}\right\rfloor\) 最多只有 \(2 \times \sqrt{n}\) 种数字。
证明,分类讨论:
- 若 \(i \leq \sqrt{n}\),只有 \(\sqrt{n}\) 个数,\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 显然只有 \(\sqrt{n}\) 个。
- 若 \(i > \sqrt{n}\),因为 \(\left\lfloor\dfrac{n}{i}\right\rfloor < \sqrt{n}\),也只有 \(\sqrt{n}\) 个。
所以我们这样计算复杂度为 \(\mathcal{O}(\sqrt{n})\)。
若有 \(l\),我们考虑求出最大的 \(r\),使 \(\left\lfloor\dfrac{n}{l}\right\rfloor = \left\lfloor\dfrac{n}{r}\right\rfloor\)
我们令 \(k = \left\lfloor\dfrac{n}{l}\right\rfloor = \left\lfloor\dfrac{n}{r}\right\rfloor \leq \dfrac n r \Longrightarrow \left\lfloor\dfrac{n}{k}\right\rfloor \geq \left\lfloor\dfrac n {\frac n r} \right\rfloor = r\)
得 \(r_{\max} = \left\lfloor\dfrac n {\left\lfloor\frac n l\right\rfloor}\right\rfloor\),一个细节是优势需要与 \(n\) 取 \(min\)。
这样只需累加 \(\left(S(r) - S(l-1)\right) \times (r-l+1)\) 即可,其中 \(S(n) = \displaystyle\sum_{i=1}^nf(i)\)。
1.2 拓展
1.2.1 多维整除分块
即求
只需令 \(r = \min\limits_{j=1}^m \left(\left\lfloor\dfrac {n_j} {\left\lfloor\frac {n_j} l\right\rfloor}\right\rfloor\right)\)。
使所有 \(\left\lfloor\dfrac {n_j} l \right\rfloor = \left\lfloor\dfrac {n_j} {r} \right\rfloor\),复杂度 \(\mathcal{O}\left(\sum\sqrt{n_j}\right)\)。
1.2.2 常用式子
\(\sum\limits_{i=1}^n i^2 = \dfrac{n(n+1)(2n+1)} 6\),\(\sum\limits_{i=1}^n i^3 = \left(\dfrac{n(n+1)} 2\right)^2\),\(\left\lceil\dfrac n m\right\rceil = \left\lfloor\dfrac {n+m-1} m\right\rfloor\),\(n \bmod i = n - i \times \left\lfloor\dfrac n i\right\rfloor\)
1.3 例题
拆分问题则答案为 \(S(r) - S(l-1)\),考虑求 \(S(n)\)。
求前 \(n\) 个数的约数和,我们考虑枚举约数 \(i\),可知前 \(n\) 个数中有 \(\left\lfloor\dfrac n i\right\rfloor\) 个数是 \(i\) 的倍数。
则每个约数 \(i\) 的贡献为 \(i\times\left\lfloor\dfrac n i\right\rfloor\),
答案就是 \(\sum\limits_{i=1}^n i\times\left\lfloor\dfrac n i\right\rfloor\)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll l,r;
ll ask(ll n){
ll ans = 0;
for(ll l = 1,r;l <= n;l = r + 1){
r = min(n,n / (n / l));
ans += (r - l + 1) * (l + r) / 2 * (n / l);
}
return ans;
}
int main(){
scanf("%lld%lld",&l,&r);
printf("%lld\n",ask(r)-ask(l-1));
return 0;
}
\(\displaystyle\sum\limits_{i=1}^n k \bmod i = \sum\limits_{i=1}^n k - i \times \left\lfloor\dfrac k i\right\rfloor= k\times n - \sum\limits_{i=1}^n \left\lfloor\dfrac k i\right\rfloor = k\times n - \sum\limits_{i=1}^{min(n,k)}\left\lfloor\dfrac k i\right\rfloor\)
直接求即可。
求 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^m(n\bmod i) \times (m \bmod j),i\neq j\)
则原式可写为\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m (n\bmod i) \cdot(m \bmod j) - \sum\limits_{i=1}^n(n\bmod i) \cdot(m \bmod i)\\\)
先考虑减号前的式子。
都可直接解决。减号后。
相当于二维整除分块。复杂度 \(O(\sqrt{n})\)。
注意 \(19940417\) 不是质数,需要别的方法求 \(2\) 与 \(6\) 的逆元。
2. 分块
2.1 算法简介
分块是一种思想,实质是将通过将一个序列分成几块,对于块内 预处理,或者说 整体修改 与 暴力重构。
对于一个序列,将每块假设有 \(B\) 个元素,则一共有 \(\frac n B\) 块,在修改时遇到 整块 就打标记,遇到 散块 就暴力重构,这样每次暴力重构的复杂度最多为 \(\mathcal{O}(B)\),打标记的复杂度最多为 \(\mathcal{O}\left(\dfrac n B\right)\),总复杂度为 \(\mathcal{O}\left(\dfrac n B + B\right)\),利用均值不等式容易得到最优复杂度为 \(\mathcal{O}(\sqrt{n})\)。
当然,每道题的操作不同,整块 与 散块 的复杂度也会不同,\(B\) 的最优复杂度也会不同,具体问题需要具体分析。
分块的优点是容易思考,比较暴力以及可以维护一些 复杂的信息。
2.2 其他分块
2.2.1 值域分块
类似于值域线段树,当值域较小时可用分块来维护值域,值域分块可以用来 平衡复杂度。
在单点修改,区间查询中。
值域分块可以 \(\mathcal{O}(1)\) 修改, \(\mathcal{O}(\sqrt{n})\) 查询,即正常单点改,区间暴力查。
也可以 \(\mathcal{O}(\sqrt{n})\) 修改,\(\mathcal{O}(1)\) 查询,即维护 块内前缀和,差分查询。
这样在某些题中可以取得更优的复杂度,详见莫队例 III。
2.2.2 块状链表
可插入的序列,设块长为 \(B\)。
根号重构: 当一个块内添加新元素后达到了 \(2B\) 时,将该块拆为两大小为 \(B\) 的块。
这样最多分裂 \(\mathcal{O}\left(\dfrac n B\right)\) 次,且每块大小都在 \([B,2B]\) 间。
2.2.3 树分块
对于一棵树,我们设一个阈值为 \(B\),考虑在树上选择不超过 \(\dfrac n B\) 个 关键点,使得每个关键点到其最近祖先关键点距离不超过 \(B\)。
一个简单的方法是随机撒点,期望下是正确的。
严谨的,我们每次选择深度最深的非关键点,考虑其 \(1 \sim B\) 级祖先是否是关键点,若都不是,则我们将其 \(B\) 级祖先设为关键点,这样每次我们可以排除至少 \(B\) 个点,关键点数量即为 \(\frac n B\) 的。
这样对于一个链询问,我们可以分成四个长度 \(< B\) 的散块,以及个数不超过 \(\dfrac n B\) 个的整块,复杂度是正确的。
2.3 经典问题
区间修改,区间查询。
对于修改,散块 暴力改,整块 打标记。
对于询问,散块 暴力加,整块 直接加和,注意加上标记。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+10,M = 330;
const ll inf = 1e17;
int n,m;
ll a[N],sum[M],la[M];
int L[N],R[N],bl[N],B,t;
void prework(){
B = sqrt(n);t = (n-1)/B+1;
for(int i = 1;i <= t;i++)L[i] = (i-1)*B+1,R[i] = min(i*B,n);
for(int i = 1;i <= n;i++)bl[i] = (i-1)/B+1,sum[bl[i]] += a[i];
}
void modify(int l,int r,ll x){
int p = bl[l],q = bl[r];
if(p == q){
for(int i = l;i <= r;i++)a[i] += x,sum[p] += x;
return;
}
for(int i = l;i <= R[p];i++)a[i] += x,sum[p] += x;
for(int i = L[q];i <= r;i++)a[i] += x,sum[q] += x;
for(int i = p+1;i <= q-1;i++)la[i] += x;
}//区间修改
ll ask(int l,int r){
int p = bl[l],q = bl[r];
ll ans = 0;
if(p == q){
for(int i = l;i <= r;i++)ans += a[i] + la[p];
return ans;
}
for(int i = l;i <= R[p];i++)ans += a[i] + la[p];
for(int i = L[q];i <= r;i++)ans += a[i] + la[q];
for(int i = p+1;i <= q-1;i++)ans += la[i] * (R[i] - L[i] + 1) + sum[i];
return ans;
}//区间查询
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)scanf("%lld",&a[i]);
prework();
for(int i = 1;i <= m;i++){
int op,l,r;ll x;
scanf("%d%d%d",&op,&l,&r);
if(op == 1){
scanf("%lld",&x);
modify(l,r,x);
}
else printf("%lld\n",ask(l,r));
}
return 0;
}
2.4 例题
II P2801 教主的魔法
区间加,区间询问 \(\geq x\) 的数的个数。
发现 \(c\) 的值很大,考虑分块,预处理时将每块内排序,假设每块有 \(B\) 个。
对于修改,整块 中,打上标记即可,复杂度 \(\mathcal{O}(\frac n B)\);散块 中,直接修改,暴力重构即可,复杂度 \(\mathcal{O}(B\log n)\)。
对于询问,整块 中,块内是重构后排好序的,二分查找即可,复杂度 \(\mathcal{O}(\frac n B\log n)\);散块 中,暴力判断即可,复杂度 \(\mathcal{O}(B)\)。
总复杂度 \(\mathcal{O}\left(n\log n + mB\log n + \dfrac {nm\log n} B\right)\),取 \(B = \sqrt{n}\) 得复杂度为 \(\mathcal{O}(n\log n + m\sqrt{n}\log n)\)。
虽然分块不优,但是可以做个示例。
我们分块后,维护每个块内的 哈希表,询问对于 散块 暴力判断,整块 则询问哈希表,修改为哈希表复杂度。
若用 map
,复杂度 \(\mathcal{O}\left(mB + \dfrac {nm\log n} B\right)\),若仅仅取 \(B = \sqrt{n}\),则 TLE 80pts 3.64s。
若平衡复杂度取 \(B = \sqrt{n\log n}\),则仅跑 1.56s,若换成更快的哈希表,如 gp_hash_table
,仅需跑 600ms。
可以直观的感受平衡复杂度的思想。
区间查询众数,输出最小众数,强制在线。
\(a\) 的值域很大,先离散化下。
考虑预处理,设 \(s[i][j]\) 为块 \(i\) 到块 \(j\) 的众数,设 \(cnt[i][j]\) 为数 \(i\) 前 \(j\) 块出现的次数,这样可以差分。
都可 \(\mathcal{O}(nB)\) 求出。
考虑询问:
- 对于整块,直接提出 \(s\) 数组内众数即可。
- 对于散块,在桶内暴力统计,暴力比较 整块与散块 中个数和。
时间复杂度 \(\mathcal{O}(n\sqrt{n})\),空间复杂度 \(\mathcal{O}(n\sqrt{n})\)。
但是仅仅这样还是不 毒瘤,lxl 卡了卡内存,变成了
IV P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III
只能要 \(O(n)\) 的内存。首先把 \(s\) 变为区间众数的次数。
发现是 \(cnt\) 的问题,考虑优化。可以建立一个 \(vector\) 存每个数出现的下标,这样求区间 \([l,r]\) 中 \(i\) 出现的次数只需要两次二分即可。
这样空间复杂度降到了 \(\mathcal{O}(n)\),不过时间复杂度变为了 \(\mathcal{O}(n\sqrt{n\log n})\)。
这样就行了吗?不行!
本题必须严格根号复杂度才可过,继续考虑优化。
在询问中设当前整块的答案为 \(ans = s[p+1][q-1]\),若左部分散块的元素 \(x\) 在范围内的个数大于 \(ans\),则在 \(vector\) 中一定有 \(ve[x][d_i+ans]\) 的值小于等于 \(r\)(这里 \(d_i\) 指 每个元素在相应vector里的下标),直接暴力 \(ans++\)。右部分同理。
\(d\) 数组可以预处理,又因为散块中的数最多有 \(2\sqrt{n}\) 个,所以复杂度合理。
时间复杂度为严格 \(\mathcal{O}(n\sqrt{n})\),空间复杂度为 \(\mathcal{O}(n)\)。
V P6177 Count on a tree II/【模板】树分块
树分块,选定阈值 \(B\),找不超过 \(\frac n B\) 个 关键点,使其与其最近关键点祖先距离不超过 \(B\),用 \(2.2.3\) 的方法,复杂度 \(\mathcal{O}(nB)\)。
然后考虑求答案,首先可以离散化,\(n\) 很小,考虑 bitset
,我们预处理任意在一条到根的链上的 一对关键点 之间的 bitset
数组,复杂度 \(\mathcal{O}\left(\dfrac {n^3}{B^2\omega}\right)\)。
对于一个询问 \(x \to y\),我们将其分为 \(x \to lca\) 与 \(y \to lca\) 两个等价段。
我们暴力跳到一个关键点,记录 bitset
,然后向上接着跳关键点,最后再跳到 lca,复杂度 \(\mathcal{O}\left(\dfrac {nm} {\omega} + mB + \dfrac {nm} B\right)\)。
总复杂度 \(\mathcal{O}\left(nB + \dfrac {n^3}{B^2\omega} + \dfrac {nm} {\omega} + mB + \dfrac {nm} B\right)\),取 \(B = \sqrt{n}\),得最优复杂度为 \(\mathcal{O}\left(\dfrac {n^2 + nm}{\omega} + (n + m)\sqrt{n}\right)\)
3. 莫队
莫队————优雅的暴力
3.1 算法简介
莫队是一种 离线 算法,资瓷修改。
主要思想:对询问分块(相当于对 \(l\) 分块),将询问在以 \(l\) 所在块为第一关键字排序,再以 \(r\) 为第二关键字升序。同时维护两个指针 \(l\) 与 \(r\),根据询问伸缩区间,这样时间复杂度就为 \(\mathcal{O}(n\sqrt{n} \times W)\),\(W\) 表示指针移动的复杂度。
证明:因为在一块内 \(r\) 为升序,最坏复杂度为 \(\mathcal{O}(n)\),\(l\) 只在块内,最坏复杂度为 \(\mathcal{O}(B^2)\),总 \(\mathcal{O}\left(\dfrac {n^2} B + nB\right)\),取 \(B = \sqrt{n}\) 复杂度最优。得证。
- 奇偶性排序:当 \(l\) 在奇数块内 \(r\) 以降序排序,否则内 \(r\) 以升序排序,会使 \(r\) 呈类似折线形,可有效减少常数。
3.2 带修莫队
因为是离线,莫队一般不资瓷修改,但我们可以暴力增加一个 时间维,同 \(l,r\) 指针一起伸缩。
询问变成了三维,排序也要多以个关键字,类似普通莫队,我们以 \(\mathcal{O}(n^{\frac 2 3})\)为块长。
我们分成了 \(n^{\frac 1 3}\) 块,第一关键字 \(l\) 所在块排序,第二关键字以 \(r\) 所在块排序,第三关键字是时间。
这样复杂度是 \(\mathcal{O}(n^{\frac 3 5})\)。
证明:设 \(n,m,q\) 同阶。
- \(l,r\) 所在块不变,\(t\) 是单增的,复杂度为 \(\mathcal{O}(n)\),总 \(\mathcal{O}\left(\dfrac {n^3} {B^2}\right)\)。
- 对于 \(l\),在块内移动最多 \(\mathcal{O}(B)\) 次,总 \(\mathcal{O}(nB)\)。
- 对于 \(r\),\(l\) 所在块不变,复杂度为 \(\mathcal{O}(B)\),\(l\) 所在块变,最坏移动 \(\mathcal{O}(n)\) 次,复杂度为 \(\mathcal{O}\left(\frac {n^2} B\right)\)。
- 总复杂度 \(\mathcal{O}\left(\dfrac {n^3} {B^2} + nB + \dfrac {n^2} B\right)\),大约取到 \(B = n^{\frac 2 3}\) 时最小,为 \(\mathcal{O}\left(n^{\frac 5 3}\right)\)。
3.3 回滚莫队
当维护的信息在区间增加与区间减小其中之一无法实现的情况下(如区间最大值),此时我们就需要 回滚莫队。
虽然不能够缩短或伸长,但增加或撤销操作也都是可以实现的。
具体操作(以不能缩短为例):
- 首先我们需要对序列 分块,然后以 \(l\) 所在块为第一关键字排序,以 \(r\) 为第二关键字排序。
- 如果 \(l,r\) 在同一块内,则暴力查询(类似分块)。
- 如果 \(l,r\) 不在同一块,设 \(l\) 所在块为 \(T\),则将 \(l\) 伸缩为 \(R[t]+1\),将 \(r\) 伸缩为 \(R[t]\)。
- \(r\) 点在块内只增加,而 \(l\) 有可能会是乱序的,则我们需要在完成该次询问后撤销回去,使 \(l\) 重回 \(R[t]+1\),形象的:可以再加一个变量 \(l_1\) 代替 \(l\) 进行该询问的增加,而 \(l\) 本身不变,即完成了 "撤销"。
类似的,若信息不能增加,则可以使 \(r\) 为第二关键字降序,在不同块内将 \(l,r\) 设为 \(L[t],n\) 的 大区间即可。
3.4 树上莫队
莫队只能在序列里使用,我们考虑把树的路径转化为序列。
欧拉序:在 \(dfs\) 每次到达某个点时,在序列中加入该点。离开某个点时,再加入一次,得到长为 \(2n\) 的序列。
我们称 \(in[x]\) 为 \(x\) 在该序列第一次的位置, \(out[x]\) 为 \(x\) 在该序列第二次的位置。
则 \(u \rightarrow v\) 的路径就是:
- 若 \(u\) 是 \(v\) 的祖先,则是 \(in[u]\) 到 \(in[v]\)。
- 若 \(u,v\) 互不为祖先,则是 \(out[u]\) 到 \(in[v]\) 再加上 \(\mathrm{lca}(u,v)\)。
这样可以把询问也改到序列上。
则我们只需要在转化后的序列跑普通莫队即可,当然带修也可以。
注意长度为 \(2n\)。
3.5 莫队二次离线
很厉害,对于前文我们所说的一般莫队,复杂度为 \(\mathcal{O}(n\sqrt{n} \times W)\),\(W\) 表示的移动某个端点如 \(r \to r + 1\) 的复杂度。
一般 \(W\) 所需复杂度都比较低,但是当 \(W\) 较大时,则需要 莫队二次离线 这个科技,可以将一般莫队的复杂度变为 \(\mathcal{O}(n\sqrt{n} + nW)\),十分的优秀。
但是需要一些限制,如贡献可以 差分 或者 。。。
假设贡献可以差分,设 \(f(l,r,r+1)\) 表示加入 \(a_{r+1}\) 这个点后对答案的贡献,则 \(f(l,r,r+1) = f(1,r,r+1) - f(1,l - 1,r+1)\),前半部分可以预处理。
难办的是后半部分,考虑再次离线,将这些 \(f\) 挂到 \(l - 1\) 这个位置,然后扫描,则若我们可以进行 \(\mathcal{O}(W)\) 修改,\(\mathcal{O}(1)\) 查询,则最后复杂度即为 \(\mathcal{O}(n\sqrt{n} + nW)\)。
左端点同理,处理后缀即可。
3.6 例题
给定序列,求在 \([l,r]\) 内随机抽取两个数相等的概率。
显然总概率为 \(\dbinom {r-l+1} 2\)。
若当前数为 \(x\),考虑增点,则需要一个桶 \(buc\) 记录 ,增加的概率就是 \(buc_x\);考虑减点,则减少的概率就是 \(buc_x - 1\)。
在区间伸缩时,必须先 伸长 再 缩短。
数据伸缩是简单的。考虑修改,维护时间轴 \(t\) 一起伸缩,可以先 删除原有数据,再添加修改数据 即可。
复杂度 \(\mathcal{O}\left(n^{\frac 5 3}\right)\)。
可以离线,考虑莫队,先排序询问。
然后考虑指针伸缩,可以想到维护一个值域 BIT,前缀和查询,复杂度 \(\mathcal{O}(n\sqrt{n}\log n)\)。
BIT 其实是 \(\log n\) 的修改与查询,总应该是 \(\mathcal{O}(n\sqrt{n}\log n+m\log n)\)。
可以考虑平衡复杂度,利用值域分块的 \(\mathcal{O}(1) - \mathcal{O}(\sqrt{n})\)。
分析复杂度:
- 莫队 \(\mathcal{O}\left(mB + \dfrac {n^2} B\right)\),\(B = \dfrac n {\sqrt{m}}\) 时最优,为 \(\mathcal{O}(n\sqrt{m})\)。
- 值域分块 \(\mathcal{O}(m\sqrt{V})\)。
总复杂度 \(\mathcal{O}(n\sqrt{m} + m\sqrt{V})\)。
一个性质是所有数出现次数的 \(\mathrm{mex}\) 不会超过 \(\sqrt{n}\),因为若答案为 \(x\),需要至少 \(1 + 2 + \cdots + (x - 1) = \frac {x(x - 1)}{2} \approx x^2\) 个数。
所以暴力判断复杂度是正确的,套带修莫队即可。
复杂度 \(\mathcal{O}\left(q\sqrt{n} + q^{\frac 5 3}\right)\)。
V 歴史の研究
求 \([l,r]\) 内最大 \(x \times c[x]\),其中 \(c[x]\) 表示 \([l,r]\) 内 \(x\) 出现的次数。
可以发现,该信息增加是容易的,但是删除时却很困难(不知道次大值),需要用到 回滚莫队。
首先我们把序列分块,然后根据 \(l\) 所在块为第一关键字升序排序,以 \(r\) 为第二关键字升序排序。
考虑询问,若 \(l,r\) 在同一块内则直接暴力查询(注意是独立查询),否则从 \(l\) 所在块 \(T\) 开始,将 \(l,r\) 定为 \(R[T]+1,R[t]\),回滚 \(l\),\(r\) 单增即可。
树上莫队板子。
树上带修莫队。
考虑区间伸缩,若增加一个点 \(x\),则 \(sum\) 增加 \(w[cnt[c[x]]+1] * v[c[x]]\),若删除点,则 \(sum\) 减少 \(w[cnt[c[x]]] * v[c[x]]\)。
VII P4887 【模板】莫队二次离线(第十四分块(前体))
考虑莫队,当右端点变化时,例如 \(r \to r + 1\) 时,我们需要找出 \([l,r]\) 中与 \(a_{r + 1}\) 异或后 popcount
为 \(k\) 的数量,值域比较小,考虑先预处理出所有 popcount
为 \(k\) 的二进制数,最多有 \(\dbinom{14}{7} \approx 4\times 10^3\) 个。
则我们可以维护一个桶记录 \([l,r]\) 中的数的个数,只需找 \(a_{r+1}\) 与预处理的数的异或值在桶中的个数,复杂度 \(\mathcal{O}\left({14 \choose{7}}m\sqrt{m}\right)\),不能过。
设 \(f(l,r,r + 1)\) 表示 \(a_{r + 1}\) 与 \([l,r]\) 中的数异或后 popcount
为 \(k\) 个个数,可以差分,即 \(f(l,r,r + 1) = f(1,r,r + 1) - f(1,l - 1,r + 1)\),前半部分可以预处理,对于 \(f(1,l - 1,r + 1)\),考虑二次离线,将该询问挂到 \(l\) 上。
然后从左向右扫描,扫描到 \(i\) 时,维护一个桶 \(buc_x\) 表示前 \(i - 1\) 个数与 \(x\) 异或后满足条件的个数,加入一个数 \(a_i\) 复杂度为 \(\mathcal{O}\left({14 \choose{7}}\right)\)。
然后对于每个二次离线后的询问,我们可以 \(\mathcal{O}(1)\) 回答,总复杂度 \(\mathcal{O}\left(m\sqrt{m} + {14 \choose 7}n\right)\)。
3.7 参考文章:
『回滚莫队及其简单运用』(强烈推荐)