The power of Fibonacci - 题解
标签与难度
标签: 数论, 中国剩余定理, 斐波那契, 快速幂, 周期性, 循环节 难度: 2300
题目大意喵~
各位Master,下午好喵~ Amy小姐姐又给B先生出难题啦,我们快来帮帮他吧!
题目是这样哒: 我们都知道斐波那契数列 ,它的定义是 ,以及 。 现在给定两个整数 和 ,我们需要计算下面这个式子的值:
因为结果可能会非常非常大,所以我们需要把答案对 取模。 其中 可能会是一个天文数字哦,所以直接暴力循环肯定是不行的说!
解题思路分析
喵呜~ 看到这么大的 和一个奇怪的模数,本能地感觉这里面一定有数学的魔法,对吧?
首先,我们来仔细看看这个模数 。它等于 ,并不是一个质数呢。这在数论问题里是一个重要的信号!我们可以把它分解质因数:
和 是互质的!看到互质的因数,我们的小脑袋里是不是就亮起了一盏名为“中国剩余定理 (Chinese Remainder Theorem, CRT)”的灯泡呢?喵~
没错!我们可以把原问题拆解成两个子问题:
- 计算 ,得到余数 。
- 计算 ,得到余数 。
然后,我们就可以通过解下面的同余方程组,来找到唯一在 意义下的解啦:
这就像是分别从两个不同的角度看一只猫猫,最后把信息拼起来得到猫猫的全貌一样,喵~
那么,怎么解决子问题呢?比如说,如何计算 呢?(这里的 就是 或者 )
这里就要用到斐波那契数列一个超级可爱的性质啦:周期性! 斐波那契数列在任何模数 下都是有周期的,这个周期被称为 皮萨诺周期 (Pisano Period),记作 。也就是说, 这个序列会无限循环。
如果 是周期性的,那么 这个序列当然也是周期性的啦!它的周期一定是 的一个约数。为了方便,我们直接用 作为周期来计算,这是完全没问题的说。
一个已知的结论是,对于素数 的幂 ,斐波那契数列的周期是 。
- 对于 :我们知道 。所以周期是 。
- 对于 :我们知道 。所以周期是 。
好啦,现在我们知道了周期,怎么利用它来求和呢? 假设我们要计算 ,其中 ,并且 的周期是 Period。 我们可以把 分成完整的周期部分和零头部分:,这里 是完整周期的数量, 是零头。
那么总和就可以写成:
为了计算这个,我们可以预处理出周期内的前缀和数组 prefix_sum,其中 prefix_sum[k] 。 于是, 就是 prefix_sum[Period-1],而 就是 prefix_sum[r]。 所以,最终的答案就是:
这个方法是不是很清晰呢,呐?
总结一下我们的完整计划:
- 分解问题: 将模 的问题分解为模 和模 的两个子问题。
- 计算周期: 确定斐波那契数列在模 和 下的周期,分别是 和 。
- 预处理: 对每个子问题,我们都在一个周期内计算出 的前缀和。
- 计算子问题答案: 利用上面推导出的公式,快速计算出 和 。
- 合并答案: 使用中国剩余定理,将 和 合并,得到最终答案!
Let's get our paws dirty and start coding! 喵~
代码实现
#include <iostream>
#include <vector>
// 使用 long long 来防止计算过程中溢出,喵~
using int64 = long long;
// 快速幂模板,用于计算 (base^exp) % mod
// a^b mod m
int64 power(int64 base, int64 exp, int64 mod) {
int64 res = 1;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1) res = (__int128)res * base % mod;
base = (__int128)base * base % mod;
exp /= 2;
}
return res;
}
// 扩展欧几里得算法,用来求解 ax + by = gcd(a, b)
// 我们用它来求模逆元,这是CRT的关键一步!
int64 extended_gcd(int64 a, int64 b, int64 &x, int64 &y) {
if (a == 0) {
x = 0;
y = 1;
return b;
}
int64 x1, y1;
int64 gcd = extended_gcd(b % a, a, x1, y1);
x = y1 - (b / a) * x1;
y = x1;
return gcd;
}
// 模逆元函数
// 求 a 在模 m 下的逆元
int64 mod_inverse(int64 a, int64 m) {
int64 x, y;
int64 g = extended_gcd(a, m, x, y);
if (g != 1) return -1; // 不存在逆元
return (x % m + m) % m;
}
// 核心计算函数:计算在某个模数下的总和
// n, m 是题目输入, modulus 是模数, period 是斐波那契数列在该模数下的周期
int64 solve_for_mod(int64 n, int64 m, int64 modulus, int period) {
// 预处理斐波那契数和我们要求的前缀和
std::vector<int64> fib(period);
std::vector<int64> prefix_sum(period);
// 计算周期内的斐波那契数
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i < period; ++i) {
fib[i] = (fib[i - 1] + fib[i - 2]) % modulus;
}
// 计算 F_i^m 的前缀和
// F_0^m = 0^m = 0 (当 m>0)
prefix_sum[0] = 0;
for (int i = 1; i < period; ++i) {
int64 term = power(fib[i], m, modulus);
prefix_sum[i] = (prefix_sum[i - 1] + term) % modulus;
}
// 利用周期性计算总和
int64 num_periods = n / period;
int64 remainder_len = n % period;
int64 sum_one_period = prefix_sum[period - 1];
// 注意这里要用 __int128 防止 num_periods * sum_one_period 溢出
int64 total_sum = ((__int128)num_periods * sum_one_period) % modulus;
total_sum = (total_sum + prefix_sum[remainder_len]) % modulus;
return total_sum;
}
int main() {
// 为了更快的输入输出,喵~
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int64 n, m;
std::cin >> n >> m;
// 我们的两个子问题
int64 mod1 = 512;
int period1 = 768; // Pisano period for 2^9
int64 mod2 = 1953125;
int period2 = 7812500; // Pisano period for 5^9
int64 ans1 = solve_for_mod(n, m, mod1, period1);
int64 ans2 = solve_for_mod(n, m, mod2, period2);
// 中国剩余定理 (CRT) 合并答案
// 我们有 x = ans1 (mod mod1) 和 x = ans2 (mod mod2)
// 根据CRT,解为 x = ans1*M2*inv(M2,mod1) + ans2*M1*inv(M1,mod2)
// 其中 M1=mod2, M2=mod1
// 简化后,一个常见的形式是 x = ans1 + mod1 * k
// 代入第二个方程: ans1 + mod1 * k ≡ ans2 (mod mod2)
// k * mod1 ≡ ans2 - ans1 (mod mod2)
// k ≡ (ans2 - ans1) * inv(mod1, mod2) (mod mod2)
int64 inv_mod1_mod2 = mod_inverse(mod1, mod2);
int64 k = ((__int128)(ans2 - ans1 + mod2) % mod2 * inv_mod1_mod2) % mod2;
int64 final_ans = ans1 + (__int128)k * mod1;
int64 final_mod = 1000000000;
std::cout << (final_ans % final_mod + final_mod) % final_mod << std::endl;
return 0;
}复杂度分析
时间复杂度: 我们的主要计算开销在于两个
solve_for_mod函数。在每个函数中,我们都需要预处理一个周期内的斐波那契数()和前缀和。计算前缀和时,对每个数都要做一次快速幂,所以这部分是 。因此,总的时间复杂度由两个周期的长度决定,即 ,其中 ,。这是一个固定的、与输入 无关的预处理时间,所以非常快!空间复杂度: 我们需要存储两个周期的斐波那契数和前缀和数组。所以空间复杂度就是 。虽然 比较大,但在现代计算机的内存限制下是完全可以接受的,喵~
知识点总结
这道题真是一次奇妙的数论探险呢!我们来总结一下这次旅程中学到的魔法吧:
- 中国剩余定理 (CRT): 当遇到一个合数模数时,可以尝试将其分解为互质的几个数的乘积,然后对每个数单独求解,最后用CRT合并答案。这是处理这类问题的标准武器之一!
- 斐波那契数列的周期性 (皮萨诺周期): 斐波那契数列在任何模数下都是周期性的。这个美妙的性质是解决涉及斐波那契和巨大下标 的问题的关键。
- 周期序列求和: 对于一个周期为 的序列,其前 项和可以通过
(n/P) * (一个周期的和) + (n%P 项的和)快速计算。预处理前缀和数组是实现这一点的绝佳方式。 - 扩展欧几里得算法: 它是实现CRT和求解模逆元的基础工具,是数论工具箱里必不可少的一把瑞士军刀,呐。
- __int128: 在处理
long long相乘可能爆long long的情况时,使用__int128是一个非常方便的技巧,可以避免复杂的、手写的龟速乘。
希望这篇题解能帮助你更好地理解这些有趣的数学知识!下次遇到难题也别怕,我们一起动动小脑筋,一定能解决的!喵~