数论八之快速乘

一、龟速乘

因其为其时间复杂度为O(logn) 所以又名龟速乘

  • 快速幂和龟速乘法主要是用来解决中间运算结果过大导致long long类型都会溢出的问题,具有一定的相似之处。
  • 快速幂将指数运算分解为乘法运算,而龟速乘法将乘法运算变为加法运算。
  • 同理将k看做一个二进制数,a每次乘以2再根据二进制k的该位情况来决定是否将a加入结果,初始a对应二进制k的最低位(右)。
#include<iostream>

using namespace std;

typedef long long LL

const int mod = 1e9 + 7;

inlint LL slow_mul(LL a, LL k, LL p)
{
    LL res = 0;
    while(k)
    {
        if(k & 1)res = (res + a) % p;
        k >>= 1;
        a = 2 * a % p;
    }
    return res;
}

int main()
{
    LL a, b;
    cin >> a >> b;
    cout << slow_mul(a, b, p);
    return 0;
}


二、光速乘

因其时间复杂度为O(1),所以称为光速乘

 

模板


//a * b % mod
inline LL mul_mod(LL a,LL b, LL mod) //快速乘
{
    return (a*b-(LL)((long double)a/mod*b)*mod+mod)%mod;
}
/*
*	光速乘步骤拆分
*	() % p
*	(a * b - c) % p
*	c = (ll)(d + e)
*	d = ((long double)a / mod * b) * p
*	e = p;
*/
 

//在非负数的环境下可以用这个来卡常:
inline LL mul(LL a,LL b,LL p){
	LL r = a*b-(LL)((long double)a/p*b+0.5)*p;
	return r < 0 ? r + p : r;
}

long long 类型的溢出其实相当于自动取相反数和自动取模。而快速乘的正确性保证就是取模的时候的模数是一定的(用脚趾头想都猜得到)

可以看到,这里用 long double 类型计算出了 a * b % p,同时避免了本来可能的一次溢出a × b ,这次溢出是可能导致计算错误的(因为后面有除法,所以不能保证正确性),显然 a / mod 与 b 相乘仍然可能导致溢出,但是这次溢出无关紧要。因为在取模可以进行除法。

而转回 long long 就是为了最后能够取模。(浮点数不可能取模)

而由于两边都可能有或没有溢出,而溢出时取的模数是一定的,所以我们将他们相见后可能会得到的答案一定在 64 位带符号长整形范围内,并且只可能是正确的余数 remainder 或 remainder-mod

实际上,并不建议在比赛中使用 O(1) 快速乘,除非你特别需要卡常数,毕竟这浮点的精度问题总有点让人不放心。 


三、快速乘总结

  • 由来:
    如果两个int相乘取模,相乘时可能会爆int,这时我们采用高一级的long long来计算。
    如果两个long long相乘取模,要用更高一级容纳位数更多的手写高精度来计算(很麻烦)。
    为了简便,人们发明了许多方法,我们称处理long long相乘取模的算法为“快速乘”。
    快速乘一般有两种方法,一种是短小精悍的O(1)算法:光速乘,一种是精准无误的O(log N)算法(龟速乘)。
  • 龟速乘思路:一个数字可以拆成许多个二进制位的0和1,即b=c_n*2^n+\cdots+c_0*2^0,这样我们把b用加法和乘法的形式表示了出来。由于c的值只有0/1,所以我们就把乘法转变成了加法。
    用分配律展开a*b=a*(c_n*2^n+\cdots+c_0*2^0)=a*c_n*2^n+ \cdots +a*c_0*2^0,只要在每次计算后立刻取模,就可以在long long范围内解决。
  • 光速乘思路:简单来说,a*b \mod p=a*b-\left \lfloor a*b/p \right \rfloor *p,注意正负就行。
  • 区别:
    (1)在效率上,显然光速乘的效率远高于龟速乘
    (2)在精度上,因为光速乘使用了 long double 类型,所以光速乘的精度不一定可靠


四.关于卡常数问题的补充

内联函数 —— C 中关键字 inline 用法解析_知秋一叶-CSDN博客_c语言inline

关于卡常那些事_Melancholy的博客-CSDN博客

C++竞赛常用实用代码(3)_偶耶的博客-CSDN博客_光速乘 龟速乘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值