快速幂 & 龟速乘 & 快速乘
龟速乘和快速乘都是为了防止模数大于int, 导致爆long long的情况
关于O(1)快速乘和关于其特判的原因 - :Dra - 洛谷博客 (luogu.com.cn)
快速幂
原理
利用二进制的思想, 把a的k次幂中的k变成一个二进制数
然后根据二进制数的每一位1把a的k次幂求出来
例如
其中二进制中的每一位
这样就是
并且因为其分开乘的性质, 是可以算取模p
后的值的
代码
int qmi(int a, int k, int p) { int res = 1; while (k) { if (k & 1) res = (1ll * res * a) % p; k >>= 1; a = (1ll * a * a) % p; } return res; }
解释一下代码
res
是用来存放我们凑出的数使用的, 也就是最后的结果, 因为是乘法, 所以它初
始为1 (一个数的0次幂也是1, 而且res == 0
会导致乘法全是0, 因此res初始为1),
中间的 a = (1ll * a * a) % p;
就是倍增求出二进制中a ^ ki
的过程, while (k) k >>= 1
这段话会老老实实的把k拆成二进制, if (k & 1)
则会把二进制中的每一位1取出来
这两个联动起来就是, 先看看k
的最后一位是1还是0(if
), 判断后, 把k的最后一位去掉(k >>= 1
),
进入下一次循环, 再看看这时候的最后一位是1还是0(if
), 直到k等于0, 这时候k的每一位我们也都判
断过了, a在这个过程中也会不断倍增(a = (1ll * a * a) % p
), 变成对应的a ^ ki,.
(这里a的变化可以手动模拟试试) (这里的最后一位是指比如1011中最右边的一位)
if (k & 1) res = (1ll * res * a) % p;
在if成立后res就会乘上这个答案, 也就是上面说的原理
补充
这个代码就是最常用的快速幂模版, 因为一个数的k次幂很容易直接爆int, 所以大多数都会取模
而两个int相乘, 在模数在int范围内下, 也很容易爆int, 所以要乘上1ll
转换为long long
类型
当然在模数超过int时, 直接乘就会爆掉long long, 这时候就会用到下面的龟速乘 或者 快速乘了
PS: 还有__int128
欢迎你
龟速乘
龟速乘和快速幂一样, 都是利用了二进制的原理,
把 a * k 的 k拆成二进制数, 根据每一位凑出来
如k = 5 = 101(2)
就可以用a + 4a凑出来
#代码如下
LL qmul(LL a, LL k, LL p) // 龟速乘 { LL res = 0; while (k) { if (k & 1) res = (res + a) % p; a = (a + a) % p; k >>= 1; } return res; }
快速乘
注意不能对于太大的模数用这个算法1e10 ~ 1e12为宜
这玩意我也是新学, 但确实很巧妙
原理就是a % b == a - a/b * b
关于O(1)快速乘和关于其特判的原因 - :Dra - 洛谷博客 (luogu.com.cn)
这篇博客讲的好
rt = a * b - (LL)((double)a / p * b + 0.5) * p;
先看这句话, 后面(LL)((double)a / p * b + 0.5)
因为(double)
的原因
所以不会爆掉, 前面a * b
和 后面再乘一个p
就会爆掉longlnog
但是没关系, 我们只需要它的差值, p是模数, p一定在longlong范围内
(这也是, 这个技巧正确的原因)
那么a * b - (LL)((double)a / p * b + 0.5) * p;
的结果一定也在longlong内
因为a % b == a - a/b * b
这个原理
这就把结果求出来了, 但是因为double 会有精度问题
在(double)a / p * b
中会损失精度, 它实际上就是接近未知的x的一个数 , 但始终达不到
注意double变longlong会把小数点拦腰截断, 也就是下取整
如果精度损失使它(double)a / p * b
超过x且它下取整后的结果比x大1,
那么就会让rt成为负数
如果精度损失使他小于x, 同理就会让rt变成大于mid的正数
所以我们会得到一个和正确答案差值绝对值为mod或0的答案
考虑当
(long double)a / mod * b
的真实值是一个十分接近但小于某个整数 x 的值, 在计算过程中的精度损失会使其超过 x 并且使向下取整后的结果大 1, 同理其十分接近但大于某个整数会使结果小1
, 故我们得到的答案与正确值之差的绝对值是mod
或 0.
这里引用博客中的话
这里就可以看看那个+0.5的作用了
这相当于四舍五入, 这会使得计算出来的rt不会比正确答案大, 而少判断一种情况, 减少常数
如果不加的话就需要判断让rt大于mid的情况
#代码
LL fqmul(LL a, LL b, LL p) // 快速乘 { LL rt = a * b - (LL)((double)a / p * b + 0.5) * p; if (rt < 0) rt += p; // 判断小于0的情况 return rt; } // 或者 LL fqmul(LL a, LL b, LL p) { LL rt = a * b - (LL)((double)a / p * b) * p; if (rt < 0) rt += p; if (rt >= mid) rt -= mid; return rt; }
模版
#include <iostream> #include <cstring> using namespace std; typedef long long LL; LL n = 142, m = 12343, mod = 1e10 + 7; LL fqmul(LL a, LL b, LL p) // 快速乘 { LL rt = a * b - (LL)((double)a / p * b + 0.5) * p; if (rt < 0) rt += p; return rt; } LL qmul(LL a, LL k, LL p) // 龟速乘 { LL res = 0; while (k) { if (k & 1) res = (res + a) % p; a = (a + a) % p; k >>= 1; } return res; } LL qmi(LL a, int k, LL p) // 快速幂 { LL res = 1; while (k) { if (k & 1) res = res * a % p; k >>= 1; a = a * a % p; } return res; } LL l_qmi(LL a, int k, LL p) // 龟速_快速幂 { LL res = 1; while (k) { if (k & 1) res = qmul(res, a, p); k >>= 1; a = qmul(a, a, p); } return res; } LL f_qmi(LL a, int k, LL p) // 快速_快速幂 { LL res = 1; while (k) { if (k & 1) res = fqmul(res, a, p); k >>= 1; a = fqmul(a, a, p); } return res; } int main() { cout << qmi(n, m, mod) << endl; // 会溢出 爆long long cout << l_qmi(n, m, mod) << endl; // O(logn * logn) cout << f_qmi(n, m, mod) << endl; // O(logn) return 0; }
神奇的 __int128
像上面的什么龟速乘, 快速乘, 其都要写一个函数, 龟速太慢, 快速有概率爆掉
在满足有__int128的情况下, 可以试试强转
__int128
如下代码, 这样即保证了效率(可普通乘法一样), 又稳定, noip可以使用
能用的话尽量先用它
LL qmi(LL a, LL k, LL p) { LL res = 1; while (k) { if (k & 1) res = (__int128)res * a % p; k >>= 1; a = (__int128)a * a % p; } return res; }
PS: __int128 原生只支持四则运算, 即 + - * / 不包含%与正常的输入(如cin, scanf)
和正常的输出(cout, printf),一般会搭配快读快写来使用。
注意 __int128 确实是比 int 和 long long 慢,但是也没有那么离谱。大概是 1.1 ~ 1.2 倍。况且编译器还会优化,开 O2 之后就更小了。所以可以放心用。当然为了保险,还是不要卡时限用这玩意。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】