快速幂算法全景总结图:从“愚公移山”到“细胞分裂”

文章目录

    • 第一讲:从"愚公移山"到"细胞分裂"
      • [1. 前提小叙:我们在算什么?](#1. 前提小叙:我们在算什么?)
      • [2. 痛点:普通方法的极限在哪里?](#2. 痛点:普通方法的极限在哪里?)
      • [3. 破局:聪明人的"细胞分裂"法](#3. 破局:聪明人的“细胞分裂”法)
      • [4. 数学上的"细胞分裂":底数平方,指数折半](#4. 数学上的“细胞分裂”:底数平方,指数折半)
      • 第一讲:高频面试题与解析
    • [第二讲:魔法背后的齿轮 ------ 二进制如何拯救奇数](#第二讲:魔法背后的齿轮 —— 二进制如何拯救奇数)
      • [1. 前提小叙:商人与他的神秘砝码](#1. 前提小叙:商人与他的神秘砝码)
      • [2. 遭遇奇数:提取"零头"放入总库](#2. 遭遇奇数:提取“零头”放入总库)
      • [3. 惊人的巧合:与二进制的完美重合](#3. 惊人的巧合:与二进制的完美重合)
      • 第二讲:高频面试题与解析
    • [第三讲:化繁为简 ------ 位运算与 C++ 代码实现](#第三讲:化繁为简 —— 位运算与 C++ 代码实现)
      • [1. 前提小叙:程序员的"读心术"与"断舍离"](#1. 前提小叙:程序员的“读心术”与“断舍离”)
      • [2. 实战:拼装最优雅的 C++ 模板](#2. 实战:拼装最优雅的 C++ 模板)
      • [3. 字符画图解:机器是如何运转的](#3. 字符画图解:机器是如何运转的)
      • 第三讲:高频面试题与解析
    • [第四讲:带着镣铐跳舞 ------ 快速幂取模(大数安全)](#第四讲:带着镣铐跳舞 —— 快速幂取模(大数安全))
      • [1. 前提小叙:可怕的指数爆炸与变量的悲歌](#1. 前提小叙:可怕的指数爆炸与变量的悲歌)
      • [2. 苹果装箱法则:同余定理的通俗理解](#2. 苹果装箱法则:同余定理的通俗理解)
      • [3. 字符画图解:砍掉多余的枝蔓](#3. 字符画图解:砍掉多余的枝蔓)
      • [4. 实战:加入取模后的 C++ 模板](#4. 实战:加入取模后的 C++ 模板)
      • 第四讲:高频面试题与解析

第一讲:从"愚公移山"到"细胞分裂"

1. 前提小叙:我们在算什么?

在数学中,我们经常需要计算 a a a 的 b b b 次方,记作 a b a^b ab。

比如 2 3 = 2 × 2 × 2 = 8 2^3 = 2 \times 2 \times 2 = 8 23=2×2×2=8。

这里,2 是底数,3 是指数

2. 痛点:普通方法的极限在哪里?

假设现在老板交给你一个任务:"去帮我复印一份文件,我要 16 份!"

普通人(循环相乘法)的思路是"愚公移山":

拿原件复印第 1 份,再印第 2 份,再印第 3 份......直到第 16 份。

如果用计算机算 3 16 3^{16} 316,就是让计算机做 16 次乘法。

如果算 3 1000000000 3^{1000000000} 31000000000(10亿次方),计算机就要傻傻地乘 10亿次。这在实际编程中(如你参加的各类算法竞赛或项目开发)会直接导致运行超时(Time Limit Exceeded)

3. 破局:聪明人的"细胞分裂"法

我们换一种思路,假设你面前有一台带"合并"功能的复印机:

  • 第 1 步: 你先印 1 份。现在手里有 2 份(原件+复印件)。
  • 第 2 步: 你把这 2 份 同时放进机器,直接变成 4 份
  • 第 3 步:4 份 同时放进去,直接变成 8 份
  • 第 4 步:8 份 同时放进去,直接变成 16 份

你看,本来需要操作 16 次的活儿,现在只用了 4 次就搞定了!

我们用字符画来看看这两种方式的对比:

text 复制代码
【普通相乘:线性增长】
(3) -> (3x3) -> (3x3x3) -> (3x3x3x3) ... 经历 16 步 ... 累死 🐢

【快速幂思想:指数爆炸】
  [3] 
   | (自身相乘)
  [3^2] 
   | (自身相乘)
  [3^4] 
   | (自身相乘)
  [3^8] 
   | (自身相乘)
  [3^16]  ... 仅需 4 步 ... 起飞 🚀

4. 数学上的"细胞分裂":底数平方,指数折半

将上面的比喻还原成数学公式,核心法则只有八个字:底数平方,指数折半

以计算 3 16 3^{16} 316 为例:

  1. 3 16 3^{16} 316 看起来很大,但我可以把它变成 ( 3 2 ) 8 (3^2)^8 (32)8 ,也就是 9 8 9^8 98。
  2. 9 8 9^8 98 可以变成 ( 9 2 ) 4 (9^2)^4 (92)4 ,也就是 81 4 81^4 814。
  3. 81 4 81^4 814 可以变成 ( 81 2 ) 2 (81^2)^2 (812)2 ,也就是 6561 2 6561^2 65612。
  4. 最终只需算 6561 × 6561 6561 \times 6561 6561×6561 就能得出结果。

指数从 16 → 8 → 4 → 2 → 1 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 16→8→4→2→1,每一次操作都砍掉了一半的工作量。

这就是快速幂的灵魂:通过不断把底数变大(平方),来换取指数的快速缩小(除以2)。


第一讲:高频面试题与解析

面试题:

"在计算 x n x^n xn 时,为什么我们不直接写一个 for 循环乘 n n n 次?请简述快速幂算法相较于暴力循环的优势,并说明两者的时间复杂度。"

详细解析与答案:

  • 缺陷剖析: 直接使用 for 循环连乘 n n n 次,意味着算法的执行步数与指数 n n n 完全成正比。如果 n n n 非常大(例如 10 9 10^9 109 级别),循环将会执行十亿次,远远超出了现代计算机在 1 秒内能处理的指令条数(通常在 10 7 ∼ 10 8 10^7 \sim 10^8 107∼108 级别),必然导致程序超时。
  • 快速幂优势: 快速幂采用了分治(折半)的思想。它每次将当前的底数进行平方,同时将指数除以 2。这样可以将原本线性的计算步骤急剧压缩。
  • 时间复杂度对比:
  • 暴力循环法的时间复杂度为 O ( n ) \mathcal{O}(n) O(n)。
  • 快速幂算法的时间复杂度为 O ( log ⁡ n ) \mathcal{O}(\log n) O(logn)。以 n = 10 9 n=10^9 n=109 为例,暴力法需要算 10 , 000 , 000 , 000 10,000,000,000 10,000,000,000 次,而快速幂大约只需要算 ≈ 30 \approx 30 ≈30 次,性能提升是降维打击级别的。

第一讲的逻辑链到这里就梳理完毕了。但你可能会敏锐地发现一个漏洞:刚才的例子里,指数 16 , 8 , 4 , 2 16, 8, 4, 2 16,8,4,2 全都是偶数 ,每次都能完美除以 2。那如果我让你算 3 11 3^{11} 311,11 除以 2 除不尽,该怎么办?


能敏锐地发现"奇数无法完美折半"这个问题,说明你已经真正把第一讲的"底数平方,指数折半"逻辑在脑子里跑通了。

这节我们就来破解这个局。我们要搞清楚:当遇到奇数时,我们该把多出来的那份"零头"怎么办?这背后,其实藏着计算机世界最底层的美学------二进制

第二讲:魔法背后的齿轮 ------ 二进制如何拯救奇数

1. 前提小叙:商人与他的神秘砝码

在正式讲解前,我们先讲一个故事,顺便复习一下十进制与二进制的映射关系。

假设有一个聪明的商人,他卖东西从不用 1克、2克、3克......这样按顺序准备一堆砝码。他只准备了重量为 1, 2, 4, 8, 16, 32... 克的砝码(每个重量只有一个)。

如果顾客要买 11 克的东西,商人怎么称?

他不会去凑,他一眼就能看出:11 = 8 + 2 + 1

他只需要把 8克、2克、1克 的砝码放上去,就能完美称出 11 克。

这就是二进制的核心本质:世界上任何一个整数,都可以被拆分成几个互不重复的 2 的幂次方之和。

把 11 拆成 8 + 2 + 1,写成二进制就是 1011(代表 8 那个位置是 1,4 是 0,2 是 1,1 是 1)。

2. 遭遇奇数:提取"零头"放入总库

回到我们的快速幂。老板让你算 3 的 11 次方(3^11)。

我们有一个"当前底数"(一开始是 3),我们要对它进行不断地平方。同时,我们准备一个【答案总库】(初始值为 1),专门用来收集结果。

遇到奇数怎么办?很简单:把多出来的那 1 份,先乘到【答案总库】里去,剩下的偶数继续折半!

让我们一步步演示 3^11 是怎么"剥洋葱"的:

  • 第 1 步:指数 11 是奇数
    它没法直接除以 2。我们就把它拆成 3^1 * 3^10
    3^1 提取出来,乘到【答案总库】里。
    剩下的 3^10,底数平方(3 变 9),指数折半(10 变 5)。
  • 第 2 步:指数 5 是奇数
    剩下的任务是算 9^5。又是奇数!拆成 9^1 * 9^4
    9^1 提取出来,乘到【答案总库】里。
    剩下的 9^4,底数平方(9 变 81),指数折半(4 变 2)。
  • 第 3 步:指数 2 是偶数
    完美!不需要提取任何零头。
    直接底数平方(81 变 6561),指数折半(2 变 1)。
  • 第 4 步:指数 1 是奇数
    剩下的任务是算 6561^1。拆成 6561^1 * 6561^0
    6561^1 乘到【答案总库】里。
    指数变成 0,计算彻底结束!

我们用字符画把这个过程具象化:

text 复制代码
计算 3^11 的过程记录:

初始状态:【答案总库】= 1      【当前底数】= 3       【指数】= 11
-----------------------------------------------------------------
(1) 遇到 11 (奇数)
    -> 提取 3 放入总库:      【答案总库】= 1 * 3
    -> 底数平方,指数变偶折半:【当前底数】= 9       【指数】= 5

(2) 遇到 5 (奇数)
    -> 提取 9 放入总库:      【答案总库】= 3 * 9
    -> 底数平方,指数变偶折半:【当前底数】= 81      【指数】= 2

(3) 遇到 2 (偶数)
    -> 完美偶数,不提取:     【答案总库】= 3 * 9 (不变)
    -> 底数平方,指数折半:    【当前底数】= 6561    【指数】= 1

(4) 遇到 1 (奇数)
    -> 提取 6561 放入总库:   【答案总库】= 3 * 9 * 6561
    -> 指数清零,任务结束!    【当前底数】= ...     【指数】= 0
-----------------------------------------------------------------
最终结果:3 * 9 * 6561 = 177147

3. 惊人的巧合:与二进制的完美重合

现在,让我们见证奇迹的时刻。

看看我们最终乘进【答案总库】里的三个数:3,9,6561

它们分别是原底数 3 的:1 次方,2 次方,8 次方

加起来凑成了什么? 3^1 * 3^2 * 3^8 = 3^11

还记得前面商人的砝码吗?11 = 1 + 2 + 8

这绝不是巧合!

  • 当指数是奇数时,意味着当前它的二进制位是 1,我们就把当前的底数"收集"起来。
  • 当指数是偶数时,意味着当前它的二进制位是 0,我们就不收集,只让底数继续自我平方。
  • 底数不断平方的过程(3 -> 9 -> 81 -> 6561),其实就是在不断生成 3^1, 3^2, 3^4, 3^8 这些"魔法砝码"!

总结成一句话:快速幂的本质,就是把指数转化为二进制,然后把二进制位是 1 的地方对应的"底数砝码"乘起来!


第二讲:高频面试题与解析

面试题:

"在实现快速幂算法时,为什么要判断指数是奇数还是偶数?这种判断和整数的二进制表示有什么内在联系?"

详细解析与答案:

  • 为何判断奇偶: 在折半的过程中,如果指数是偶数,可以直接平滑地将底数平方、指数除以 2(例如 a 4 = ( a 2 ) 2 a^4 = (a^2)^2 a4=(a2)2);但如果指数是奇数,直接除以 2 会丢失一次乘积,因此需要将其拆分为 a n = a × a n − 1 a^n = a \times a^{n-1} an=a×an−1,先将分离出来的这一个 a a a(当前的底数状态)累乘到最终的结果变量中,使剩下的指数变为偶数,从而继续折半操作。
  • 与二进制的内在联系: 判断奇偶并除以 2 的过程,本质上就是十进制转二进制时的"除 2 取余法"
  • 指数为奇数,余数为 1,等同于该数在当前二进制最低位为 1。这表示我们需要这个权重对应的"底数累乘值"。
  • 指数为偶数,余数为 0,等同于该数在当前二进制最低位为 0。这表示我们不需要这个权重。
  • 底数每次自我平方,正好对应了二进制位权重的成倍递增( a 1 , a 2 , a 4 , a 8 ... a^1, a^2, a^4, a^8 \dots a1,a2,a4,a8...)。

到这里,快速幂的数学逻辑和底层思想你已经完全打通了。接下来,我们要将这些优雅的数学思想,翻译成你在计算机世界里最熟悉的语言:C++。


终于来到了最激动人心的一步。在上一讲中,我们弄明白了"奇数提取零头"和"二进制位拆分"的深刻联系。现在,我们要把这种数学上的美感,毫无保留地刻印到代码中。

这节我们会抛弃那些笨重的常规数学符号,用计算机最喜欢的"底层语言"来重构这套逻辑。

第三讲:化繁为简 ------ 位运算与 C++ 代码实现

1. 前提小叙:程序员的"读心术"与"断舍离"

在把逻辑翻译成代码之前,我们需要掌握两个 C++ 中的位操作符。这是因为在上一讲中,我们核心只做了两件事:判断奇偶除以 2

常规做法是 n % 2 == 1n = n / 2,但这在底层的效率不够高。既然我们已经知道这是二进制的游戏,为什么不直接操作二进制呢?

  • 操作一:读心术 & 1 (按位与)
    任何一个整数在计算机里都是一串 0 和 1。一个数是奇数还是偶数,完全由它的最后一位(最低位)决定 。最低位是 1 就是奇数,是 0 就是偶数。
    & 操作就像是一个带有透视功能的扫描仪。当我们写下 n & 1 时,就是拿 000...001 去和 n 的二进制对齐相乘。前面的所有位都被 0 屏蔽了,只暴露出最后一位!
    结论:用 if (n & 1) 代替 if (n % 2 == 1),极其高效。
  • 操作二:断舍离 >> 1 (右移一位)
    十进制里,把 123 抹掉最后一位变成 12,就相当于除以 10。
    二进制里,把 1011 向右推挤,掉出悬崖丢掉最后一位变成 101,就相当于除以 2
    结论:用 n >>= 1 代替 n = n / 2,直接从物理层面抹掉一半。

2. 实战:拼装最优雅的 C++ 模板

现在,我们把上一讲的"答案总库"和"当前底数"全部转化为代码变量。

  • ans:这就是我们的【答案总库】,一开始什么都没乘,所以初始值为 1。
  • base:这就是我们的【当前底数】,它负责不断自我平方膨胀。
  • exp:这就是【指数】,它负责不断被查探最低位,然后右移缩小。

整个流程就成了一台精密的自动售货机:

只要指数还没有被榨干(大于0) -> 看看当前最低位是不是 1 -> 是 1 就把底数收入总库 -> 底数自我膨胀 -> 指数右移一位。

来看看这段堪称艺术品的 C++ 模板:

cpp 复制代码
long long fastPower(long long base, long long exp) {
    long long ans = 1; // 答案总库,初始值为 1
    
    while (exp > 0) { // 只要指数还没有被榨干
        // 步骤 1:读心术,探查最低位
        if (exp & 1) { 
            ans = ans * base; // 如果最低位是 1,提取当前底数放入总库
        }
        
        // 步骤 2:底数自我膨胀(准备下一个二进制位的砝码)
        base = base * base; 
        
        // 步骤 3:断舍离,指数右移一位(相当于除以 2)
        exp >>= 1; 
    }
    
    return ans; // 返回最终累乘结果
}

3. 字符画图解:机器是如何运转的

让我们用上帝视角,重新跑一遍 fastPower(3, 11) 的代码执行轨迹。

注意看 exp 的二进制是如何一点点被剥离的:

text 复制代码
初始:ans = 1, base = 3, exp = 11 (二进制 1011)

【第一轮循环】
  |-- exp = 101[1]  -> 发现最低位是 1!触发 (exp & 1)
  |-- 动作1:提取放入总库 -> ans = 1 * 3 = 3
  |-- 动作2:底数膨胀 -> base = 3 * 3 = 9
  |-- 动作3:断舍离 -> exp 右移一位变成 101 (十进制 5)

【第二轮循环】
  |-- exp = 10[1]   -> 发现最低位是 1!触发 (exp & 1)
  |-- 动作1:提取放入总库 -> ans = 3 * 9 = 27
  |-- 动作2:底数膨胀 -> base = 9 * 9 = 81
  |-- 动作3:断舍离 -> exp 右移一位变成 10 (十进制 2)

【第三轮循环】
  |-- exp = 1[0]    -> 发现最低位是 0!跳过 (exp & 1)
  |-- 动作1:ans 不变,依然是 27
  |-- 动作2:底数膨胀 -> base = 81 * 81 = 6561
  |-- 动作3:断舍离 -> exp 右移一位变成 1 (十进制 1)

【第四轮循环】
  |-- exp = [1]     -> 发现最低位是 1!触发 (exp & 1)
  |-- 动作1:提取放入总库 -> ans = 27 * 6561 = 177147
  |-- 动作2:底数膨胀 -> base = 6561 * 6561 = 43046721
  |-- 动作3:断舍离 -> exp 右移一位变成 0

【结束】 exp = 0,循环终止。返回 ans = 177147。

看,没有任何多余的动作,每一次循环都极其干净利落。


第三讲:高频面试题与解析

面试题:

"在书写快速幂的代码时,为什么资深工程师通常更倾向于使用位运算(& 1>> 1)来代替取模(% 2)和除法(/ 2)?"

详细解析与答案:

  • 指令层级的降维打击: 在计算机底层的 ALU(算术逻辑单元)中,加减法和位运算是最基础、执行最快的指令,通常只需要 1 个 CPU 时钟周期。而整数的除法和取模运算是由微代码或者多步电路实现的复杂操作,往往需要几十个时钟周期。使用位运算是在用 CPU 的"母语"和它交流,能榨干最后的性能。
  • 编译器的友好性: 虽然现代优秀的编译器(如 GCC/Clang 在 -O2 优化下)遇到 n / 2 并且已知 n 是无符号整数时,会自动帮你优化成右移。但作为算法开发者,主动书写位运算能保证在任何编译环境下(包括未优化的环境),代码都能以最优性能运行。
  • 语义更契合: 快速幂的本质就是在遍历指数的二进制位,使用位操作符(按位与、右移)在逻辑语义上比算术操作符(取模、除法)更加贴切,能够直接彰显算法的本质思想。

到了这一步,你已经掌握了一把异常锋利的宝剑。但是,这把剑在遇到现实世界的问题时,可能会"自毁"。因为指数爆炸太快,long long 类型最多只能装下大概 18 位十进制数字,稍微大一点的计算就会导致"数据溢出",变成一堆负数或乱码。

要解决这个问题,我们需要给这把剑套上一个坚固的"剑鞘" ------ 取模运算


在日常刷题提交评测时,你一定经常碰到题目最后附带这样一句话:"由于结果过大,请将最终答案对 10 9 + 7 10^9+7 109+7 取模"。

这就是我们要戴上的"镣铐"。为什么要有这道镣铐?又该如何在镣铐下优雅地跳舞?这就是第四讲要彻底搞懂的内容。

第四讲:带着镣铐跳舞 ------ 快速幂取模(大数安全)

1. 前提小叙:可怕的指数爆炸与变量的悲歌

在 C++ 中,即使是我们能用的最大整数类型 long long,它的极限大约是 9 × 10 18 9 \times 10^{18} 9×1018。看起来很大对吧?

但是,如果是计算 3 40 3^{40} 340,结果就已经逼近 long long 的极限了。如果你想算 3 100 3^{100} 3100,变量会直接"撑爆"(也就是内存溢出),多出来的数据会被截断,最终输出一堆毫无意义的乱码或者负数。

为了防止溢出,算法题目通常会给一个"模数"(比如 10 9 + 7 10^9+7 109+7)。取模(%)的意思就是求余数。

问题来了:我们是应该全部算完最后再取模 ,还是边算边取模

2. 苹果装箱法则:同余定理的通俗理解

全部算完再取模,绝对会死在半路上 ,因为中间的底数膨胀时,早把变量撑爆了。

所以,我们必须边算边取模 。但这样做结果还准确吗?这就引出了数学中非常重要的同余定理

我们用"装箱子"来打个比方:

假设每个箱子最多装 10 个苹果(相当于对 10 取模)。

  • 张三有 12 个苹果,装满 1 箱,剩下 2 个散装的。
  • 李四有 13 个苹果,装满 1 箱,剩下 3 个散装的。

现在他们想把苹果数量相乘,看看总共能剩下几个散装的。

  • 方法一(老实人): 12 × 13 = 156 12 \times 13 = 156 12×13=156。把 156 个苹果去装箱,装满 15 箱,剩下 6 个。
  • 方法二(聪明人): 反正整箱的最后还是整箱的,对余数没有影响。我们直接把他们各自的散装苹果相乘: 2 × 3 = 6 2 \times 3 = 6 2×3=6。不用去算庞大的 156,也能得到余数 6

用严谨的公式表达,就是:

( A × B )   m o d   M = ( ( A   m o d   M ) × ( B   m o d   M ) )   m o d   M (A \times B) \bmod M = ((A \bmod M) \times (B \bmod M)) \bmod M (A×B)modM=((AmodM)×(BmodM))modM

这就是在镣铐下跳舞的秘诀:在乘法的每一步之后,立刻砍掉溢出的"整箱",只带着"散装余数"轻装上阵进入下一步!

3. 字符画图解:砍掉多余的枝蔓

我们以计算 3 8   m o d   10 3^8 \bmod 10 38mod10 为例,看看"不取模"和"步步取模"的区别:

text 复制代码
【不取模的狂飙:体积几何级膨胀】
3 -> 9 -> 81 -> 6561 (越往后数字越大,随时面临撑爆风险) 💥

【步步取模的修剪:永远保持小巧】 (模数 = 10)
  [3] 
   | 平方,即 3*3 = 9,取模 10 
  [9] 
   | 平方,即 9*9 = 81,取模 10,砍掉 80
  [1] 
   | 平方,即 1*1 = 1,取模 10
  [1]  ... (永远在安全范围内) 🛡️

4. 实战:加入取模后的 C++ 模板

基于第三讲的模板,我们只需要在每一次发生乘法的地方 ,加上 % mod 就可以了。请看这段经得起任何测试数据考验的安全代码:

cpp 复制代码
long long fastPowerMod(long long base, long long exp, long long mod) {
    long long ans = 1; 
    base = base % mod; // 防御性动作:防止一开始的底数就已经大于模数
    
    while (exp > 0) { 
        if (exp & 1) { 
            // 动作1:提取放入总库时,做一次乘法,立刻取模
            ans = (ans * base) % mod; 
        }
        
        // 动作2:底数自我膨胀时,做一次乘法,立刻取模
        base = (base * base) % mod; 
        
        // 动作3:指数右移不变
        exp >>= 1; 
    }
    
    return ans;
}

注意到了吗?代码的逻辑框架完全没有变,只是在乘法运算后加了一把名为 % mod 的"安全锁"。


第四讲:高频面试题与解析

面试题:

"在实现带取模的快速幂时,常见的模数(如 10 9 + 7 10^9+7 109+7)有什么特殊含义?在上述模板中,ans * base 这两项在相乘的一瞬间,是否有可能造成 long long 溢出?"

详细解析与答案:

  • 模数为何经常是 10 9 + 7 10^9+7 109+7: 第一,它是一个足够大的质数,在后续学习组合数学、哈希表、求逆元时,质数能保证良好的数学性质(如满足费马小定理)。第二,它的大小极具讲究: 10 9 + 7 10^9+7 109+7 接近 10 9 10^9 109。
  • 乘法瞬间的溢出分析: 在我们严格执行"步步取模"的策略下,ansbase 两个变量的值永远不会超过模数 10 9 + 7 10^9+7 109+7。
    当它们在代码中执行 ans * basebase * base 时,可能发生的最大相乘情况约为:
    10 9 × 10 9 = 10 18 10^9 \times 10^9 = 10^{18} 109×109=1018。
    前面提到过,C++ 中 64 位整型 long long 的最大上限约为 9 × 10 18 9 \times 10^{18} 9×1018。
    因此, 10 18 10^{18} 1018 刚好稳稳地落在 long long 的安全接球区内!这正是算法设计者选择 10 9 + 7 10^9+7 109+7 的良苦用心------它既足够大,又保证了平方时绝对不会引发 64 位整型的溢出。

相关推荐
七号驿栈1 小时前
深入理解 MAC 消息认证算法:原理、分类与应用
算法
bnmoel1 小时前
数据结构深度剖析二叉树・下篇:链式结构的实现 ,遍历方法全解析
c语言·数据结构·算法·二叉树·
豆瓣鸡1 小时前
算法日记 - Day5
java·算法
Accerlator1 小时前
RAG 评测
算法
小鬼头编程1 小时前
信息学竞赛体系(CSP-J/S、NOIP、NOI、IOI)
c++·人工智能·青少年编程
是江迪呀1 小时前
关于记笔记的批评和自我批评
笔记·aigc·ai编程
码哥DFS1 小时前
算法练习day1-备战2027届秋招
前端·javascript·数据结构·算法
ACP广源盛139246256732 小时前
GSV6155@ACP# Type-C 视频信号转换芯片:技术架构、工程痛点适配与落地场景分析
大数据·c语言·开发语言·人工智能·分布式·嵌入式硬件·架构
不会代码的小猴2 小时前
电机学笔记
笔记