夜深人静写算法(三十)- 二分快速幂

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;
}