三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

错位相减法:从七层宝塔问题到算法竞赛中的等差乘等比数列求和

错位相减法:从七层宝塔问题到算法竞赛中的等差乘等比数列求和

1. 背景与核心概念

在算法学习、数学建模乃至编程面试中,我们常常会遇到一类特殊的数列求和问题:等比数列与等差数列的乘积求和。这类问题直接硬算往往计算量巨大,而“错位相减法”正是解决此类问题的利器。它并非一个复杂的数学定理,而是一种巧妙、高效的代数运算技巧,能将一个看似复杂的求和式,转化为一个简单的等比数列求和问题。

本文将以一个经典的数学名题——“七层宝塔红灯问题”作为引例和实战场景,彻底拆解错位相减法的原理、通用公式推导、编程实现以及其在算法竞赛中的应用。无论你是正在备战蓝桥杯、ACM等竞赛的学生,还是希望提升数学思维和代码能力的开发者,掌握这个方法都将让你在应对复杂数列求和时游刃有余。

七层宝塔红灯问题(原题大意):一座宝塔共有七层,每层悬挂的红灯数是上一层的2倍。已知顶层有1盏红灯,请问整座宝塔共悬挂了多少盏红灯?

这本质上是一个首项为1、公比为2的等比数列求和问题:S = 1 + 2 + 4 + 8 + 16 + 32 + 64。我们可以轻松地用等比数列求和公式解决。但题目稍作变化,难度便陡增:如果每层的红灯数不是上一层的2倍,而是“层数乘以2的(层数-1)次方”呢?即第 n 层的红灯数为 n * 2^(n-1)。求七层宝塔的总红灯数。这就变成了一个典型的“等差乘等比”型数列求和,正是错位相减法大显身手的舞台。

2. 问题抽象与数学模型

首先,让我们将具体问题抽象为通用数学模型。

我们要求和的数列通项公式为:a_n = n * q^(n-1)。其中:

  • n是项数(对应层数),从1开始。
  • q是等比部分的公比(在红灯问题中,q=2)。
  • n是等差部分(一个公差为1的等差数列)。

设数列的前 n 项和为S_nS_n = 1 * q^0 + 2 * q^1 + 3 * q^2 + ... + n * q^(n-1)

我们的目标就是求出S_n关于nq的闭合表达式(即一个可以直接计算的公式)。

为什么不能直接求和?因为每一项都包含变量nq^(n-1)的乘积,随着 n 增大,直接累加的计算复杂度是 O(n)。当 n 很大(例如 10^9)时,这是不可接受的。错位相减法的目标,就是将这个 O(n) 的求和,转化为 O(1) 的公式计算。

3. 错位相减法原理详解

错位相减法的核心思想是“构造一个与原和式相似的等式,通过对齐‘错位’相减,消去中间项,最终解出和式”。我们通过步骤来演示:

第1步:写出原始和式 SS = 1 * q^0 + 2 * q^1 + 3 * q^2 + ... + (n-1) * q^(n-2) + n * q^(n-1)

第2步:构造等比因子倍乘后的和式 qS将上式两边同时乘以公比qqS = 1 * q^1 + 2 * q^2 + 3 * q^3 + ... + (n-1) * q^(n-1) + n * q^n

观察SqS,你会发现它们的项非常相似,但指数错开了一位。这就是“错位”的含义。

第3步:执行错位相减我们用S减去qS(为了得到正数结果,通常用qSS,这里我们遵循常规):S - qS = (1*q^0 + 2*q^1 + 3*q^2 + ... + n*q^(n-1)) - (1*q^1 + 2*q^2 + ... + (n-1)*q^(n-1) + n*q^n)

将等号右边对齐书写:

S = 1*q^0 + 2*q^1 + 3*q^2 + ... + (n-1)*q^(n-2) + n*q^(n-1) qS = 1*q^1 + 2*q^2 + ... + (n-2)*q^(n-2) + (n-1)*q^(n-1) + n*q^n

现在用第一行减第二行:S - qS = 1*q^0 + [(2-1)*q^1] + [(3-2)*q^2] + ... + {[n - (n-1)]*q^(n-1)} - n*q^n

化简括号内的系数:S - qS = 1 + q^1 + q^2 + ... + q^(n-1) - n * q^n

第4步:识别并利用等比数列求和上式等号右边从q^0q^(n-1)正好是一个首项为1、公比为q、项数为n的等比数列之和(记作T)。 等比数列求和公式为:T = (q^n - 1) / (q - 1),当q != 1时。

因此:S - qS = T - n * q^n = (q^n - 1)/(q - 1) - n * q^n

第5步:解出目标 S左边S - qS = S(1 - q)。 所以,S(1 - q) = (q^n - 1)/(q - 1) - n * q^n

注意到(q^n - 1)/(q - 1) = -(1 - q^n)/(1 - q)。代入上式并两边同时除以(1 - q)

S = [ (q^n - 1)/(q - 1) - n * q^n ] / (1 - q)

为了得到一个更整洁的形式,分子分母同时乘以 -1:S = [ n * q^n - (q^n - 1)/(q - 1) ] / (q - 1)

最终,我们得到通用公式:S_n = [n * q^n - (q^n - 1)/(q - 1)] / (q - 1),其中q != 1

q = 1时,原数列变为a_n = n * 1^(n-1) = n,即等差数列求和S_n = n(n+1)/2

4. 实战:解决七层宝塔红灯问题

现在,我们将公式应用于改编后的红灯问题。 已知:n = 7(7层),q = 2。 求S_7

方法一:公式法代入公式:S_7 = [7 * 2^7 - (2^7 - 1)/(2 - 1)] / (2 - 1)计算步骤:

  1. 2^7 = 128
  2. 7 * 128 = 896
  3. (128 - 1)/1 = 127
  4. 896 - 127 = 769
  5. 769 / 1 = 769

所以,宝塔共有769盏红灯。

方法二:编程验证(Python)我们可以写一个简单的程序来验证公式的正确性。

def sum_by_brute_force(n, q): """暴力循环求和,用于验证公式""" total = 0 for i in range(1, n + 1): # i 从 1 到 n total += i * (q ** (i - 1)) return total def sum_by_formula(n, q): """使用错位相减法推导的公式求和""" if q == 1: return n * (n + 1) // 2 # 等差数列求和 q_pow_n = q ** n numerator = n * q_pow_n - (q_pow_n - 1) / (q - 1) return numerator / (q - 1) # 解决红灯问题 n = 7 q = 2 result_brute = sum_by_brute_force(n, q) result_formula = sum_by_formula(n, q) print(f"暴力循环求和结果:{result_brute}") print(f"公式计算求和结果:{result_formula}") print(f"两者是否相等?{result_brute == result_formula}")

运行上述代码,输出将是:

暴力循环求和结果:769 公式计算求和结果:769.0 两者是否相等?True

公式计算结果是浮点数769.0,因为公式推导过程中涉及了除法。对于整数参数,结果实际上是整数,在比较时需要注意类型。在实际编程竞赛中,如果模数M是质数,我们通常会在模M意义下使用公式,并利用快速幂和乘法逆元来计算除法。

5. 算法竞赛中的优化与模运算处理

在算法竞赛中,nq可能非常大(如n <= 10^18),并且结果通常要求对一个质数MOD(如10^9+7)取模。直接计算q^n会溢出,必须使用快速幂算法,并且要处理公式中的除法,即乘以分母的乘法逆元

假设MOD是一个质数(如1000000007),且q % MOD != 1。我们需要计算:S_n % MOD = [n * q^n - (q^n - 1) * inv(q-1)] * inv(q-1) % MOD其中,inv(x)表示x在模MOD下的乘法逆元,满足(x * inv(x)) % MOD = 1。计算逆元可以用费马小定理:inv(x) = pow(x, MOD-2, MOD)

下面是完整的C++实现示例:

#include <iostream> using namespace std; typedef long long ll; const int MOD = 1000000007; // 快速幂取模:计算 (base^exp) % mod ll qpow(ll base, ll exp, ll mod) { ll res = 1; base %= mod; // 防止base过大 while (exp > 0) { if (exp & 1) res = (res * base) % mod; base = (base * base) % mod; exp >>= 1; } return res; } // 计算逆元:费马小定理,要求 mod 是质数且 a 与 mod 互质 ll inv(ll a, ll mod) { return qpow(a, mod - 2, mod); } // 使用错位相减法公式计算 S = sum_{i=1}^{n} i * q^{i-1},结果对 MOD 取模 ll sum_of_series(ll n, ll q) { if (q % MOD == 1) { // 特殊情况:q ≡ 1 (mod MOD),此时数列是等差数列 // S = n*(n+1)/2 % MOD // 需要计算 2 的逆元 ll inv2 = inv(2, MOD); return ((n % MOD) * ((n + 1) % MOD) % MOD) * inv2 % MOD; } ll qn = qpow(q, n, MOD); // q^n % MOD ll inv_qminus1 = inv((q - 1 + MOD) % MOD, MOD); // (q-1)的逆元,注意处理负数 // 公式:S = [n * q^n - (q^n - 1) * inv(q-1)] * inv(q-1) % MOD ll term1 = (n % MOD) * qn % MOD; ll term2 = (qn - 1 + MOD) % MOD * inv_qminus1 % MOD; ll numerator = (term1 - term2 + MOD) % MOD; // 防止负数 ll result = numerator * inv_qminus1 % MOD; return result; } int main() { // 测试:计算 n=7, q=2 的结果 ll n = 7, q = 2; ll ans = sum_of_series(n, q); cout << "S(" << n << ", " << q << ") mod " << MOD << " = " << ans << endl; // 输出 769 return 0; }

关键点解析:

  1. 快速幂 (qpow):用于高效计算q^n % MOD,时间复杂度 O(log n),可处理巨大的n
  2. 乘法逆元 (inv):将公式中的除法/(q-1)转化为乘以(q-1)的逆元,这是模运算下的标准操作。
  3. 负数处理:在模运算中,(a - b) % MOD可能得到负数,需要加MOD再取模确保结果非负,如(term1 - term2 + MOD) % MOD
  4. 特判q == 1:当公比为1时,公式分母为零,需单独处理为等差数列求和。

6. 常见问题与排查思路

在理解和应用错位相减法时,经常会遇到以下几个问题:

问题现象常见原因解决思路
公式计算结果与暴力枚举对不上1. 公式推导错误(如符号错误)。
2. 编程实现时,数列首项或指数处理错误(例如误将通项写成n * q^n)。
3. 边界条件n=0q=1未处理。
1. 重新推导公式,用n=2,3的小例子手工验证。
2. 检查代码中的循环起点和幂次:第i项是i * q^(i-1)
3. 添加对n=0(和为0)和q=1(等差数列)的特判。
模运算下结果错误或出现负数1. 中间运算溢出(未及时取模)。
2. 计算逆元时,底数a与模数MOD不互质(如a % MOD == 0)。
3. 未处理减法可能产生的负数。
1. 确保乘法和加法每步之后都取模。
2. 确保模数MOD是质数,且q-1不为MOD的倍数。若(q-1)%MOD==0,则属于q≡1的特例,走等差数列分支。
3. 减法后加MOD再取模。
对于超大n(如1e18)程序超时使用了 O(n) 的循环来求和。必须使用 O(log n) 的公式法。确保qpow函数正确实现了快速幂。
不理解“错位”如何操作对乘以公比q后产生的式子结构不清晰。在纸上严格按照步骤写出SqS,上下对齐项,观察系数和指数的对应关系。减法的目的是消去中间项,只留下首尾少数项。

7. 最佳实践与工程建议

掌握错位相减法后,如何在工程和竞赛中用好它?

  1. 先抽象,后套用:遇到求和问题,先分析通项公式。只要是(等差数列) * (等比数列)的形式(如(an+b) * q^(n-1)),都可以尝试用错位相减法。更一般地,对于P(n) * q^nP(n)是n的多项式),可以通过多次错位相减(即对S多次乘以q后相减)来求解。
  2. 手工推导与代码验证结合:对于重要的公式,不要完全依赖记忆。在理解原理的基础上,可以用小规模数据(如n=3,4)手工推导并编程暴力验证,确保公式正确无误。
  3. 模运算要谨慎
    • 步步取模:在计算过程中,特别是连乘和累加时,每一步操作后都进行取模,防止中间结果溢出(即使在C++的long long中也可能溢出)。
    • 逆元前提:使用费马小定理求逆元时,必须确保模数是质数,且底数与模数互质。在非质数模数下,需要使用扩展欧几里得算法求逆元。
    • 处理特殊值:务必特判q % MOD == 1的情况,这是公式的奇点。
  4. 封装为工具函数:在竞赛代码库中,将qpowinvsum_of_series函数封装好。这样在比赛时可以直接调用,节省时间并减少出错。
  5. 扩展到更一般情况:错位相减法的思想可以推广。例如,求Σ i^2 * q^(i-1),可以对S = Σ i * q^(i-1)的结果再次应用错位相减的思想,或者直接对原和式乘以q后错位相减两次。这要求更强的代数变形能力,但核心思路一致。

8. 总结

错位相减法是一个将技巧性实用性完美结合的数学工具。它通过构造、错位、相减、化简四步,优雅地将一个O(n)的求和问题降维为O(1)或O(log n)的公式计算。

我们从经典的“七层宝塔”问题出发,一步步推导了通用公式S_n = [n*q^n - (q^n-1)/(q-1)] / (q-1),并提供了从直接计算、Python验证到C++模运算实现的完整代码路径。更重要的是,我们探讨了在算法竞赛中处理大数和模运算的关键细节:快速幂、乘法逆元以及边界条件处理。

下次当你看到形如Σ P(n) * r^n的求和式时,不要急于编写循环。不妨先思考:能否用错位相减法将其“降服”?掌握这一思想,不仅能让你在编程竞赛中多一份从容,更能深刻体会到数学变换在优化算法中的巨大威力。

← 返回列表