分块与根号算法入门

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 多维整除分块

即求

\[\sum_{i=1}^{n} f(i)\prod_{j=1}^{m} \left\lfloor\frac {n_j} i\right\rfloor \]

只需令 \(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 例题

I P2424 约数和

拆分问题则答案为 \(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;
}

II P2261 [CQOI2007] 余数求和

\(\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\)

直接求即可。

代码

IIIP2260 [清华集训2012] 模积和

\(\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)\\\)
先考虑减号前的式子。

\[\sum\limits_{i=1}^n\sum\limits_{j=1}^m (n\bmod i) \cdot (m \bmod j) = \left(n^2 - \sum\limits_{i=1}^n i\cdot \left\lfloor\dfrac n i\right\rfloor\right)\cdot\left(m^2 - \sum\limits_{j=1}^m j\cdot \left\lfloor\dfrac n j\right\rfloor\right) \]

都可直接解决。减号后。

\[\begin{aligned}\sum\limits_{i=1}^n(n\bmod i) \times (m \bmod i) &= \sum\limits_{i=1}^n\left(n - i\cdot\left\lfloor\dfrac n i\right\rfloor\right) \times \left(m - i\cdot\left\lfloor\dfrac m i\right\rfloor\right) \\ &= \sum_{i=1}^n\left(nm - n\cdot i\left\lfloor\dfrac m i\right\rfloor - m\cdot \left\lfloor\dfrac n i\right\rfloor + i^2\left\lfloor\dfrac n i\right\rfloor\left\lfloor\dfrac m i\right\rfloor\right) \end{aligned}\]

相当于二维整除分块。复杂度 \(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 例题

I P3372 【模板】线段树 1

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)\)

代码

III P2464 [SDOI2008] 郁闷的小 J

虽然分块不优,但是可以做个示例。

我们分块后,维护每个块内的 哈希表,询问对于 散块 暴力判断,整块 则询问哈希表,修改为哈希表复杂度。

若用 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。

可以直观的感受平衡复杂度的思想。

代码

IV P4168 [Violet] 蒲公英

区间查询众数,输出最小众数,强制在线。

\(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 例题

I P1494 [国家集训队] 小 Z 的袜子

给定序列,求在 \([l,r]\) 内随机抽取两个数相等的概率。

显然总概率为 \(\dbinom {r-l+1} 2\)

若当前数为 \(x\),考虑增点,则需要一个桶 \(buc\) 记录 ,增加的概率就是 \(buc_x\);考虑减点,则减少的概率就是 \(buc_x - 1\)

在区间伸缩时,必须先 伸长缩短

代码

II P1903 [国家集训队] 数颜色 / 维护队列

数据伸缩是简单的。考虑修改,维护时间轴 \(t\) 一起伸缩,可以先 删除原有数据,再添加修改数据 即可。

复杂度 \(\mathcal{O}\left(n^{\frac 5 3}\right)\)

代码

III P4396 [AHOI2013] 作业

可以离线,考虑莫队,先排序询问。

然后考虑指针伸缩,可以想到维护一个值域 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})\)

代码

IV Machine Learning

一个性质是所有数出现次数的 \(\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\) 单增即可。

代码

VI COT2 - Count on a tree II

树上莫队板子。

代码

VII P4074 [WC2013] 糖果公园

树上带修莫队。

考虑区间伸缩,若增加一个点 \(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 参考文章:

「分块」数列分块入门1 – 9 by hzwer

分块相关杂谈

『回滚莫队及其简单运用』(强烈推荐)

posted @ 2024-04-12 18:16  oXUo  阅读(289)  评论(2)    收藏  举报
网站统计