AcWing

题目链接
今天学习lyd的蓝书学到一种新的解法,以前只会用龟速乘做,没想到还可以利用整型溢出来做,真是太妙了!
1.首先是龟速乘的做法,和快速幂思想差不多,就不多说了

ll solve(ll a, ll b, ll p) {
    ll res = a, ans = 0;
    while(b) {
        if (b&1) ans = (ans+res)%p;
        res = (res<<1)%p;
        b >>= 1;
    }
    return ans%p;
}

2.然后就是另一种解法了,首先,我们假设(a imes bpmod p)不会溢出,那么这个公式等价于(a imes b-lfloor a imes bdiv p floor imes p)(为什么要搞这么个公式过会儿再讲)。好了,现在我们考虑一下会溢出
的情况,因为补码的性质,两数相乘超过64个进制位的数自会自动溢出(相当于模unsigned long long),但是没有关西,因为我们要模p,而p最大不过1e18,所以我们的最终答案也一定不大于1e18,所以我们只要求出等式左半部分和右半部分的差就可以得到答案了。
现在我们最大的问题就是如何求(lfloor a imes bdiv p floor)这一部分啦。这里有个巧妙的办法就是先让a和b去模p,那么a和b模p后的乘积一定不会大于(p^2),我们用long double来存这个数,long double的有效数字是18~19个数字,超过的话会自动舍弃低位数字,前面说过了,我们a和b模p之后的结果不大于(p^2),而之后我们还要除以p,所以只有前面p个数字对我们来说有价值的,刚好,long double的位数满足我们的需求!所以我们现在就能求出来等式两边的数了,这时候他们的差就是我们所要的结果。(可能有的同学还对利用溢出计算不太理解,你可以理解为我们对左右两边计算的结果模(2^{64})然后做差)

ll solve(ll a, ll b, ll p) {
    a %= p, b %= p;
    ll c = (long double)a*b/p;
    ll ans = a*b - c*p;
    return (ans+p)%p;
}

3.我随后天真以为既然利用溢出计算,那么干嘛那么麻烦,这样不就行了吗hhhhhh

ll solve(ll a, ll b, ll p) {
    a *= b;
    if (a<0) a = -a;
    return a%p;
}

嗯...然后喜提wonderful answer, 我们(apmod p)相当于求(lfloor adiv p floor imes p), 显然我们这么求溢出之后的a和原数根本不是一个数,那么相除的结果也不对(如果用long double)会面临精度不够的问
题,所以这就是为什么要用上面的公式了

原文地址:https://www.cnblogs.com/shuitiangong/p/12504637.html