快速乘(模乘)

快速乘

先定义 using ll = long long int,请实现一个函数 ll mul(ll x, ll y, ll p),返回值为变量 xy p 意义下的乘积(其中 xyplong 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 乘法,因为速度碾压性的快。
posted @ 2025-02-24 18:49  zhiyin123123  阅读(55)  评论(0)    收藏  举报
目录侧边栏

qwq