前言
前几天看了一下 2017 2017 2017 年论文《非常规大小分块算法初探》,里面有一节讲到了 01 01 01 矩阵乘法,介绍了一种 O ( n 3 ω log n ) \mathcal O({n^3\over\omega\sqrt{\log n}}) O(ωlognn3) 的分块做法。然而其代码实现难度高,复杂度优化效果小,我曾向 R a i n y b u n n y \sf Rainybunny Rainybunny 说,不会真的有人写这玩意儿骗分吧!
结果我还是写了分块 01 01 01 矩阵乘法来骗分。但是并不是上面的方法。是我无意中想到的一个分块方法。为什么要写出来呢?可能也是为了留作纪念吧……
概述
这里的 01 01 01 矩阵是,在模 2 2 2 意义下(在域 G F ( 2 ) \Bbb{GF}(2) GF(2) 内)进行所有操作,包括答案矩阵也要对 2 2 2 取模。
二维分块
这是论文中的方法,先简单讲讲。不妨设要计算 A × B A\times B A×B,将两个矩形都划分成若干 S × S S\times S S×S 大小的子矩形。花费 O ( 4 S 2 S 3 ω ) \mathcal O(4^{S^2}{S^3\over \omega}) O(4S2ωS3) 的时间,预处理每一种不同的情况对应的结果。然后用 O [ ( n S ) 3 S 2 ω ] = O ( n 3 S ω ) \mathcal O[({n\over S})^3{S^2\over\omega}]=\mathcal O({n^3\over S\omega}) O[(Sn)3ωS2]=O(Sωn3) 的复杂度合并结果。取 S = log 4 n 2 S=\sqrt{{\log_4 n}\over 2} S=2log4n 时,有最优复杂度 O ( n 3 ω log n ) \mathcal O({n^3\over{\omega \sqrt{\log n}}}) O(ωlognn3) 。
一维分块
为什么上面的方法不优呢?因为 S ⩽ ω S\leqslant\omega S⩽ω,所以 b i t s e t \tt bitset bitset 的效用发挥不出来了。 b i t s e t \tt bitset bitset 才是最强的分块者!
考虑 ω \omega ω 和 S S S 的分块效果同时存在。回到原式 C i , j = ∑ k A i , k B k , j C_{i,j}=\sum_k A_{i,k}B_{k,j} Ci,j=∑kAi,kBk,j 上,不妨设 i i i 是未分块的一维。能不能对 j j j 做 S S S 分块呢?相当于矩阵 A A A 的某个行向量,要点乘 S S S 个列向量。似乎并没有冗余计算。估计行不通。
所以考虑对 k k k 做 S S S 分块。也就是 A A A 的行向量的一部分,长度仅为 S S S,代表 B B B 的某些行向量是否被选择,然后异或到第 i i i 行上。长度仅为 S S S,这提示我们 Method of four Russians \text{Method of four Russians} Method of four Russians,即讨论 2 S 2^S 2S 种不同的 A i , k ( k ∈ [ l , r ] ) A_{i,k}\;(k\in[l,r]) Ai,k(k∈[l,r]) 的情况,并直接计算出对第 i i i 行的贡献。
那么 B B B 中每 S S S 行就要暴力计算一次,这是 O ( n S ⋅ 2 S ⋅ n ω ) \mathcal O({n\over S}\cdot 2^S\cdot{n\over\omega}) O(Sn⋅2S⋅ωn) 的,而 A A A 中只需要 O ( n 2 S ) \mathcal O({n^2\over S}) O(Sn2) 次行向量操作,是 O ( n 3 ω S ) \mathcal O({n^3\over \omega S}) O(ωSn3) 的,取 S = log n S=\log n S=logn 有最优复杂度 O ( n 3 ω log n ) \mathcal O({n^3\over \omega\log n}) O(ωlognn3),且代码实现极其简单。
代码实现
n n n 是矩阵大小, s t e p step step 是块的大小,已在主函数内求出。
const int MAXN = 2000;
const int LOGN = 10;
bitset<MAXN> dp[1<<LOGN];
int lowbit[1<<LOGN], n, step;
struct Matrix{
bitset<MAXN> a[MAXN];
inline void clear(){
for(int i=0; i!=n; ++i)
a[i].reset();
}
Matrix operator * (const Matrix &b) const {
Matrix c; c.clear();
for(int l=0,r=step; l!=n; l=r,r+=step){
if(__builtin_expect(r > n, false))
r = n; // not to exceed n
for(int S=1; S!=(1<<step); ++S){
const int &id = lowbit[S];
dp[S] = dp[S^(1<<id)]^b.a[l+id];
}
for(int i=0,now=0; i!=n; ++i,now=0){
for(int j=l; j!=r; ++j)
now ^= (a[i].test(j))<<(j-l);
c.a[i] ^= dp[now]; // find in table
}
}
return c;
}
};