夜深人静写算法(三十)- 二分快速幂
Posted 英雄哪里出来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了夜深人静写算法(三十)- 二分快速幂相关的知识,希望对你有一定的参考价值。
一、前言
刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
如果你也和我有一样的困扰,那接下来就让我们一起来专攻一下数论的知识吧!额 … … 介于明天又要上班,就先来个基础的数论算法吧。二分快速幂,就当开胃菜了,适当也要劳逸结合不是嘛!
二、模幂运算
【例题一】已知三个正整数 a , b , c ( a , b , c < = 1 0 5 ) a, b, c(a , b, c<=10^5) a,b,c(a,b,c<=105),求: f ( a , b , c ) = a b m o d c f(a,b,c) = a^b \\ mod \\ c f(a,b,c)=ab mod c
- 这里求 a a a 的 b b b 次幂(次方)对 c c c 取余数的计算,被称为模幂运算,本文会针对以上的式子,根据数据范围提供完全不同的算法来进行讲解,确保正确性的前提下,以求达到时间和空间的平衡;
- 高端的算法往往只需要采用最朴素的诠释方式。
1、朴素算法
1)枚举
- 直接一个
b
b
b 次的循环,枚举对
a
a
a 的
b
b
b 次乘法:
图二-1-1 - 最后进行取模运算(c++中的 ‘%’ 符号等价于数学中的取模 m o d mod mod);
- c++代码实现如下:
int f(int a, int b, int c) {
int ans = 1;
while(b--)
ans = ans * a;
return ans % c;
}
- 这段代码有没有问题呢?我们来尝试用一些数据测试一下:
f ( 3 , 5 , 7 ) = 5 f(3, 5, 7) = 5 f(3,5,7)=5
f ( 3 , 10 , 7 ) = 4 f(3, 10, 7) = 4 f(3,10,7)=4
f ( 3 , 100 , 7 ) = − 2 f(3, 100, 7) = -2 f(3,100,7)=−2
f ( 3 , 1000000000 , 7 ) = . . . f(3, 1000000000, 7) = ... f(3,1000000000,7)=...
-
1、溢出问题:首先,在数据量比较大的时候,运算结果会出现负数,这是因为 i n t int int 的取值范围是 [ − 2 31 , 2 31 ) [-2^{31}, 2^{31}) [−231,231),超过后就会溢出,结果就和预期的不符了;
图二-1-2 -
2、时间复杂度:其次,这个算法的时间复杂度是 O ( b ) O(b) O(b) 的,当 b b b 很大的时候,明显是无法满足时间要求的,尤其是写服务器代码的时候,时间复杂度尤为重要,10个、100个、1000 个玩家出现这样的计算时,时间消耗都是成倍增长的,非常占用服务器 CPU 资源;
-
那么,我们先保证正确性,确保计算过程中不会溢出,这里就需要用到数论里面一些简单的知识:模乘。
2)模乘
- 模乘满足如下公式:
a b m o d c = ( a m o d c ) ( b m o d c ) m o d c a b \\ mod \\ c = (a \\ mod \\ c) (b \\ mod \\ c) \\ mod \\ c ab mod c=(a mod c)(b mod c) mod c - 证明如下:
- 令
a
=
r
a
+
m
a
c
a = r_a + m_ac
a=ra+mac,
b
=
r
b
+
m
b
c
b = r_b + m_bc
b=rb+mbc, 其中
(
0
<
=
r
a
,
r
b
<
c
)
(0 <= r_a, r_b < c)
(0<=ra,rb<c),将乘法代入原式,得到:
a b m o d c = ( r a + m a c ) ( r b + m b c ) m o d c = ( r a r b + r a m b c + m a r b c + m a m b c 2 ) m o d c = r a r b m o d c = ( a m o d c ) ( b m o d c ) m o d c \\begin{aligned} ab \\ mod \\ c &= (r_a + m_ac) (r_b + m_bc) \\ mod \\ c\\\\ &= (r_ar_b + r_a m_bc + m_ar_bc + m_am_bc^2) \\ mod \\ c \\\\ &= r_ar_b \\ mod \\ c \\\\ &= (a \\ mod \\ c) (b \\ mod \\ c) \\ mod \\ c \\end{aligned} ab mod c=(ra+mac)(rb+mbc) mod c=(rarb+rambc+marbc+mambc2) mod c=rarb mod c=(a mod c)(b mod c) mod c - 所以我们可以对朴素算法进行一些改进,改进如下:
int f(int a, int b, int c) {
int ans = 1 % c;
while(b--)
ans = ans * a % c;
return ans;
}
- 利用模乘运算,可以先模再乘,每次运算完毕确保数字是小于模数的,这样确保数字不会太大而造成溢出,但是这里没有考虑一种情况,就是 a a a 有可能是负数;
3)负数考虑
- 需要再进行一次改进,改进版如下:
int f(int a, int b, int c) {
a = (a % c + c) % c;
int ans = 1 % c;
while(b--)
ans = ans * a % c;
return ans;
}
- 我们发现只需要多加一句话:
图二-1-3 -
a
%
c
a \\% c
a%c 是为了确保模完以后
a
a
a 的绝对值小于
c
c
c,如图二-1-3所示;
图二-1-4 - 再加上
c
c
c 是为了保证结果是正数,如图二-1-4所示;
图二-1-5 - 最后又模上 c c c 是为了确保 a a a 是正数的情况也能准确让结果落在 [ 0 , c ) [0,c) [0,c) 的范围内,如图二-1-5所示;
- 这样改完还有哪些问题呢???
- 1、溢出问题:溢出问题仍然存在,只要 c c c 的范围是 [ − 2 31 , 2 31 ) [-2^{31}, 2^{31}) [−231,231), a n s ans ans 和 a a a 的范围就在 [ 0 , c ) [0, c) [0,c), a n s ∗ a ans * a ans∗a 的范围就是 [ 0 , c 2 ) [0, c^2) [0,c2),最坏情况就是 [ 0 , 2 62 ) [0, 2^{62}) 《夜深人静写算法》数论篇 - (13) 二分快速幂