Barrett Reduction 乘法取模加速
Sweetlemon · · 个人记录
今年写 USACO Open 的时候看到了一种神奇的乘法取模加速方法,下面简单叙述。
使用条件:
-
计算
a\times b \bmod{p} ,其中0\le a,b<p ,且p 是一个编译时未知的常数,例如题目输入的(需要多次取模的)模数。如果p 在编译时已知,可以由编译器完成编译时优化。 -
如果
p 是int级别的数,那么就需要使用__int128。
这种算法的主要思想是这样的。
通常,我们(人工)计算取模,用的是
首先我们想到一种方法,即把式子写成
这时候就有神仙提出:我们可以钦定一个整数
具体怎么求
据说是为了防止算出的商超过实际的商,我们一般取
那么
这里,我们取 至多需要再做一次减法 不需要再调整。
由于
由于
如果
总结这个算法的过程如下:
-
根据
p 的规模选取合适的k ,一般要求k\ge \lceil 2\log_2 p \rceil 。 -
根据
k,p 预处理出m=\left\lfloor \dfrac{2^k}{p} \right\rfloor 。 -
实际计算时,用
q=\dfrac{a\cdot m}{2^k} 计算出商,再用r=a-pq 得出余数。
哪里需要用到 __int128 呢?如果 __int128。此外,如果 __int128。
参考实现如下:
struct Kasumul{
ll p,m; //p 表示上面的模数, m 为取模参数
//构造一个模数为 p 的取模器
Kasumul(ll tp):p(tp),m(ll((lll(1)<<63)/tp)){}
//calc(x) 即计算 x%p 的值
ll calc(ll x){
ll q=((lll(x)*m)>>63);
ll aans=x-q*p;
MD(aans); //这行有一定必要,参见 LOJ #3300. 「联合省选 2020 A」组合数问题
return aans;
}
};
Kasumul muler(2); //因为没有 void 构造函数,所以需要用参数 2 来初始化
//在 main 函数中
const int p=10000007;
muler=Kasumul(p);
//使用时
int a=1235123,b=72343451,ans;
//下面这行代码相当于 ans=1ll*a*b%p;
ans=muler.calc(1ll*a*b);
实际测试选用了 【ZJOI2010】排列计数,使用上述优化的程序的运行时间约为通常程序的