快速乘(模乘)
快速乘
先定义 using ll = long long int,请实现一个函数 ll mul(ll x, ll y, ll p),返回值为变量 x 和 y 在模 p 意义下的乘积(其中 x,y,p 在 long long int 范围内,并且是正数,满足 \(x,y<p\))。
__int128 乘法
using ll = long long int;
ll mul(ll x, ll y, ll p) {
return (__int128)x * y % p;
}
这种实现不算慢,但也不算快,不过正确性有保证。
时间复杂度 \(O(1)\)。
快速乘法
#include <algorithm>
using ll = long long int;
namespace detail {
ll mo(ll x, ll p){
return x >= p ? x - p : x;
}
}
ll mul(ll x, ll y, ll p) {
if (x < y) std::swap(x, y);
ll ret = 0;
while (y) {
if (y & 1) ret = detail::mo(ret + x, p);
x = detail::mo(x + x, p);
y >>= 1;
}
return ret;
}
这种实现非常慢,不建议使用。
时间复杂度 \(O(\log(\min\{x,y\}))\)
long double 乘法
using ll = long long int;
namespace detail {
using lf = long double;
ll p;
lf pinv;
void init(ll _p) {
p = _p;
pinv = (lf)1.0 / p;
return ;
}
ll mul(ll x, ll y) {
ll z = x * pinv * y + 0.5;
ll ret = x * y - z * p;
return ret + (ret >> 63 & p);
}
}
ll mul(ll x, ll y, ll p) {
detail::init(p);
return detail::mul(x, y);
}
或者
using ll = long long int;
namespace detail {
using lf = long double;
ll p;
lf pinv;
void init(ll _p) {
p = _p;
pinv = (lf)1.0 / p;
return ;
}
ll mul(ll x, ll y) {
ll z = x * pinv * y + 0.5;
ll ret = x * y - z * p;
return ret < 0 ? ret + p : ret;
}
}
ll mul(ll x, ll y, ll p) {
detail::init(p);
return detail::mul(x, y);
}
这个非常的快,正确性也过拍了,但是不知道是否一定正确。
时间复杂度 \(O(1)\)。
此处 (ret >> 63 & p) 疑似 ret < 0 ? ret + p : ret。
实验数据
使用个人电脑,每次测试分别生成 \({10}^7\) 组随机的 \(x,y,p\),满足 \(1\leq x,y<p\leq V\),使用不同的实现分别测试 \(5\) 次,消耗时间取平均值。
| 实现 | \(V\) 的取值 | 时间(单位:秒) |
|---|---|---|
__int128 乘法 |
\({10}^{4}\) | 0.139251 |
| \({10}^{9}\) | 0.140033 | |
| \({10}^{12}\) | 0.332232 | |
| \({10}^{18}\) | 0.330044 | |
| 快速乘法 | \({10}^{4}\) | 0.447848 |
| \({10}^{9}\) | 1.07379 | |
| \({10}^{12}\) | 1.4065 | |
| \({10}^{18}\) | 2.13812 | |
long double 乘法 |
\({10}^{4}\) | 0.0566828 |
| \({10}^{9}\) | 0.0598728 | |
| \({10}^{12}\) | 0.0564469 | |
| \({10}^{18}\) | 0.0576331 |
结论
- 不建议使用快速乘法,因为太慢了。
- 建议使用
long double乘法,因为速度碾压性的快。

浙公网安备 33010602011771号