与其说四毛子算法(Method of Four Russians)是一种算法不如说它是一种思想,一种(非常规)分块后暴力预处理以此来优化复杂度的思想。
一类经典的问题是「加一减一 ST 表」:就是现在有一个序列,满足其差分序列只有 +1 和 -1,如何快速维护区间最小值?我们的做法是,先按照 (B=lceilfrac{log_2 n}{2} ceil) 分块,那么我们现在有 (lceil n/B ceil) 个块,只要我们能快速维护出块内每个子区间的最小值,就可以用 ST 表在 (mathcal{O}(frac{n}{B}logfrac{n}{B})=mathcal{O}(n)) 的复杂度内完成预处理。那么现在注意到块内的不同可能序列的个数最多只有 (mathcal{O}(2^B)=mathcal{O}(sqrt n)) 种,于是可以预处理出每种可能的序列的每个子区间最小值所在位置,这样就可以做到 (mathcal{O}(n)-mathcal{O}(1)) 查询。
现在来看一个进阶版的:如何 (mathcal{O}(n)-mathcal{O}(1)) 查询树上两点的 LCA?一个做法是,先求出整棵树的(扩展)欧拉序,也就是不只是在退出一个点的时候记录,而是每次访问一条边就记录两个端点。这样容易证明从一个点的第一个位置到另一个点的第一个位置中间的深度最浅的点就是它俩的 LCA,于是转化为了 RMQ 问题,而注意到这也是一个「加一减一 ST 表」,于是优化到了 (mathcal{O}(n)-mathcal{O}(1))。
终极进阶版:如何 (mathcal{O}(n)-mathcal{O}(1)) 求一个任意序列的 RMQ?先对这个序列建出笛卡尔树,于是就转化为了 LCA 问题,这是我们已经会做了的。
另一种问题是,求一张有向图的传递闭包。或者更普适一些:快速求两个 01 矩阵的积,其中加法定义为或运算,乘法定义为与运算。
不妨假定都是 (n imes n) 的方阵,于是设分块大小为 (B=lceillog_2 n ceil),然后做如下操作:将 A 划分成 (lceil frac nB ceil) 个横条(不足填零),将 B 划分成 (lceil frac nB ceil) 个纵条,然后把对应的横条和纵条做乘积,得到 (lceilfrac nB ceil) 个 (n imes n) 的方阵。那么我们有结论,这两个方阵的乘积就是所有结果加起来的和。
那么我们现在来考虑优化:对于某两条的乘法,我们可以先预处理出 A 的横条中的某一列是某种情况(共 (2^B=mathcal{O}(n)) 种)时,B 的纵条对结果的贡献(就是结果的某一列),然后每次只用累加这个结果就行了,总复杂度降到了 (mathcal{O}(frac{n^3}{log n})).