专栏文章

根号算法

算法·理论参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@min732y1
此快照首次捕获于
2025/12/01 21:38
3 个月前
此快照最后确认于
2025/12/01 21:38
3 个月前
查看原文

分块

一般用来解决区间问题。
我们考虑把区间分成若干块,对于每个我们维护该块的信息。
例如:
QQ 次操作:
  1. MM ll rr xx,表示将区间 [l,r][l,r] 的每个数都加 xx
  2. QQ ll rr,表示查询区间 [l,r][l,r] 的每个数的和。
我们可以维护每个块的和。设块长为 SS。那么对于每个操作,我们需要访问 [l,r][l,r] 包含的所有块以及 l,rl,r 所在的散块,单次操作的复杂度为 O(S+nS)O(S+\frac{n}{S})
由均值不等式可知,当 S=nSS=\frac{n}{S} 时最优,所以最优的块长为 S=nS=\sqrt{n},此时单次操作的复杂度为 O(n)O(\sqrt{n})

根号分治

在有些题目中,我们有多种暴力的方式,而每种暴力的效率与我们每次的查询有关。于是我们可以根据某个阀值 SS 采用不同方式的暴力。
这样说还是有点抽象了,还是结合例题说一下会比较清楚。
例题:P3396
我们有两种暴力方式:
  1. 每次询问暴力 O(nx)O(\frac{n}{x}) 的一个一个跳。这样的话,查询操作会在 xx 很小的时候卡到 O(n)O(n),而每次修改是 O(1)O(1) 的。
  2. 维护一个数组 sumi,jsum_{i,j},表示下标模 iijj 的数的和。每次修改的时候,我们修改 sumi,x%isum_{i,x\%i},其中 ii 为后面的查询中最大的 xx。这样的话,修改操作会在 ii 很大的时候卡到 O(n)O(n) ,而每次查询是 O(1)O(1) 的。
我们想在这两种暴力之间找到一个平衡点,于是我们根据 xxn\sqrt{n} 的大小关系,理。
  1. 维护数组 sumi,jsum_{i,j},意义与上述第二种暴力相同,但我们只维护 ini \le \sqrt{n} 的模数 ii
  2. 对于每次查询,如果 xnx \le \sqrt{n},直接输出 sumx,ysum_{x,y},复杂度 O(1)O(1) ;否则,暴力 O(nx)O(\frac{n}{x}) 的跳过去,复杂度 O(n)O(\sqrt{n})
  3. 每次修改,更新 sumi,x%isum_{i,x\%i},复杂度 O(n)O(\sqrt{n})
总复杂度 O(mn)O(m \sqrt{n})
总结:一般来说当暴力复杂度跟区间长度、字符串长度有关的时候,就可以考虑用数组或其他数据结构来维护 \len\ge \sqrt{n} 的部分,然后另一部分直接暴力,使单次复杂度不超过 O(n)O(\sqrt{n})
这就是优雅的暴力~

莫队

普通莫队

如果 [l+1,r][l+1,r][l1,r][l-1,r][l,r+1][l,r+1][l,r1][l,r-1] 的答案均可以由 [l,r][l,r] 的答案 O(1)O(1) 修改得到的话,那么该问题就可以用莫队算法来解决。
考虑分块。
我们对于每一个查询操作离线下来,然后按照查询区间的 ll 所在的块为第一关键字,按 rr 为第二关键字排序。排序后维护一个 [L,R][L,R] ,表示已经处理好的区间,对于当前查询的区间,直接暴力跳过去,记录下答案即可。
这个东西大概长成这个样子:
CPP
while (R<r) Add(++R);
while (R>r) Delete(R--);
while (L>l) Add(--L);
while (L<l) Delete(L++);

//其中Add()表示加贡献,Delete()表示减贡献
模板题:P1494
掌握上述流程之后其实就不难。
设当前要加或减的袜子的颜色是 cc,则它所带来的贡献就是 cntccnt_c,其中 cntccnt_c 表示颜色 cc 的数量。(这个可以组合数把式子展开来证,但是可以简单一点:多出来的贡献可定是要钦定选择当前的袜子的,那么为了配对成功,就需要从之前的颜色为 cc 的袜子里选一个,所以有 cntccnt_c 种选择,贡献就是 cntccnt_c。)弄一个全局的 ansans 来记录答案就行。

复杂度

RR 的移动
首先对于每一个块,求出其第一个询问的答案需要 O(n)O(n) 的时间,由于同一个块中的 rr 是排好序的,所以在同一个块中从第一个求到最后一个 rr 需要 O(n)O(n) 的时间,那么 n\sqrt{n} 个块就是 O(nn)O(n\sqrt{n})
块与块之间的 rr 是无序的,所以每次移动最坏为 O(n)O(n) 的,但由于块是排好序的,且有 n\sqrt{n} 个块,所以这样的移动最多只有 O(n)O(\sqrt{n}) 次,复杂度为 O(nn)O(n\sqrt{n})
所以 RR 移动的复杂度为 O(nn)O(n\sqrt{n})
LL 的移动
ll 是按块排过序的,所以块内相邻两个最多移动 n\sqrt{n} 次,所以复杂度为 O(mn)O(m\sqrt{n})mm 为总询问数量)。
当下一个区间的 ll 所在的块与当前所在的块不同时,最多移动 2n2\sqrt{n} 就可以到下一个 ll ,共 n\sqrt{n} 个块,所以复杂度为 O(n×n)=O(n)O(\sqrt{n} \times \sqrt{n})=O(n)
综上,总复杂度 O(nn)O(n\sqrt{n})

带修改的莫队

其实也是类似的,只不过我们多了一维 tt,表示该询问是经过 tt 次修改后的询问。
这样的话,我们按 ll 所在的块为第一关键字,按 rr 所在的块为第二关键字,tt 为第三关键字排序。还是同样维护 L,R,TL,R,T 表示上一次处理的 l,r,tl,r,t
LLRR 还是像普通莫队一样暴力跳过去,那 TT 呢?还是暴力:
  1. T<tT<t 时,暴力操作到第 tt 次。
  2. T>tT>t 时,暴力还原到第 tt 次。
当修改的位置落在当前区间内时还要记得加/减一下贡献。
但这一次块长的设置可能有些变化,变为块长 S=n23S=n^\frac{2}{3},分析和证明见OI Wiki
复杂度 O(n53)O(n^\frac{5}{3})

评论

0 条评论,欢迎与作者交流。

正在加载评论...