题解 P4000 【斐波那契数列】
飞雨烟雁
2022-04-11 13:34:37
#### 原题链接:[P4000 斐波那契数列](https://www.luogu.com.cn/problem/P4000)
------------
~~(一开始以为是道水题,没想到竟然如此复杂,呜呜呜)~~
> 题目:求 $f_n\bmod P$ 的值。
**注:** 原题的模数是小写的 $p$,此处为了区分下文的质数 $p$,改为大写的 $P$。
首先,$n$ 达到了 $10^{30000000}$ 的级别,矩阵快速幂肯定 TLE。
于是我们想到了要通过一些方法降低 $n$ 的大小,最先想到的是对 $n$ 模以某个数,然后再矩阵快速幂不就好了嘛。
那么先手算一下取模的这个数和 $P$ 的关系吧,假设 $P=4$,那么斐波那契数列长这样:
$$1,1,2,3,1,0,1,1,2,3,1,0,...$$
好像确实有规律,针对 $P=4$ 时我们只需求 $f_{n\bmod 6}$ 就好了!
可是对于其他的 $P$ 要怎么求?为了解决这个问题,我们可能需要一点前置知识……
------------
## 前置知识:二次剩余
------------
可能需先掌握「[原根](https://www.luogu.com.cn/problem/P6091)」的知识。
二次剩余是指这样一类问题:
> 已知 $n,p$ 的值,解方程 $x^2\equiv n\pmod p$ 。
洛谷上有模板题:[P5491 【模板】二次剩余](https://www.luogu.com.cn/problem/P5491)。此处不做详述,只讲与下文有关的,即 $p$ 是奇素数的情况。
首先 $p\mid n$ 时,$x^2\equiv n\equiv 0\pmod p$,故 $x=0$。
当 $p\nmid n$ 时,$x$ 可能无解,如 $n=2,p=3$ 时。那么 $n,p$ 满足什么条件时 $x$ 有解呢?
**结论一**:$n^{\frac{p-1}{2}}\equiv 1\pmod p$ 是 $x$ 有不为 $0$ 的解的充要条件。以下证明:
当 $x$ 有不为 $0$ 的解时,$x^{p-1}\equiv 1$。因此 $n^{\frac{p-1}{2}}\equiv(x^2)^{\frac{p-1}{2}}\equiv x^{p-1}\equiv 1$,必要性证毕。
当 $n^{\frac{p-1}{2}}\equiv 1$ 时,设 $g$ 是 $p$ 的原根,则存在 $k$ 使得 $n=g^k$,故 $g^{\frac{k}{2}(p-1)}\equiv 1$。因为 $g$ 的阶是 $p-1$,所以 $(p-1)|\frac{k}{2}(p-1)$,即 $k$ 是偶数。那么解得 $x\equiv g^{\frac{k}{2}}$,有不为 $0$ 的解。充分性证毕。
那什么时候无解呢?因为 $n^{p-1}\equiv 1$,$(n^{\frac{p-1}{2}}-1)(n^{\frac{p-1}{2}}+1)\equiv 0$,即 $n^{\frac{p-1}{2}}\equiv \pm1$。由结论一推理得,$n^{\frac{p-1}{2}}\equiv -1$ 是 $x$ 无解的充要条件,记为**结论二**。
事实上,上面这些对下文有用的只有这句话:
$x^2=n\pmod p$ 无解等价于 $n^{\frac{p-1}{2}}\equiv -1\pmod p$。
------------
## 斐波那契循环节
------------
接下来就是主线情节了。
**结论三**:斐波那契数列 $\{f_i\}$ 对 $p$ 取模后必定是周期数列。
显然,只要存在 $i\neq j$ 满足 $f_i\equiv f_j$ 且 $f_{i+1}\equiv f_{j+1}$。那么由递推关系可以得到 $f_{i+k}\equiv f_{j+k}$,命题就成立了。
要证明存在这样的 $i,j$ 也不难,因为不可能有无穷多对不同的 $(f_i,f_{i+1})$,至多 $p^2$ 对,因此一定存在 $i\neq j$,满足 $f_i\equiv f_j$ 且 $f_{i+1}\equiv f_{j+1}$。故循环节一定存在,且长度不超过 $p^2$,证毕。
设 $\pi(p)$ 是这个数列的周期(注意,此处并不要求 $\pi(p)$ 是最小正周期)。那要怎样求 $\pi(p)$?
不难发现,若存在 $k$ 满足 $\forall i, f_{i}\equiv f_{i+k}\pmod p$,那 $k$ 就是 $\{f_i\bmod p\}$ 的周期,取 $\pi(p)=k$ 就好了。
------------
### Part 1:
让我们从最简单的情况开始:当模数 $P$ 等于**某个质数** $p$ 的时候。
先搬出斐波那契数列的通项公式:$f_i=\dfrac{(\frac{1+\sqrt 5}{2})^i-(\frac{1-\sqrt 5}{2})^i}{\sqrt 5}$。
因为公式里有 $\sqrt 5,\frac1 2$,所以我们先单独讨论 $p=2,5$ 的情况:经计算取 $\pi(2)=3,\pi(5)=20$。
撇除这些特殊情况后,$p$ 便是一个不等于 $5$ 的奇素数。但根据二次剩余的知识,$\sqrt 5$ 模 $p$ 时不一定能表示成整数,需分类讨论。
- $x^2\equiv 5\pmod p$ 有非 $0$ 解
这时 $x$ 等价于 $\sqrt 5$ ,那么 $\sqrt 5,\frac{1}{\sqrt 5},\frac 1 2$ 模 $p$ 就都有意义,即 $\frac{1+\sqrt 5}{2},\frac{1-\sqrt 5}{2}$ 能用整数表示,适用于欧拉定理。所以:
$$\begin{aligned}f_{i+\varphi(p)}&\equiv \frac{(\frac{1+\sqrt 5}{2})^{i+\varphi(p)}-(\frac{1-\sqrt 5}{2})^{i+\varphi(p)}}{\sqrt 5}\\&\equiv\frac{(\frac{1+\sqrt 5}{2})^i-(\frac{1-\sqrt 5}{2})^i}{\sqrt 5}=f_i\end{aligned}$$
故 $\varphi(p)$ 是数列的一个周期,取 $\pi(p)= \varphi(p)=p-1$。
- $x^2\equiv 5\pmod p$ 无解
无解意味着找不到整数替代 $\sqrt 5$,需要**扩域**。
扩域指的是令某个数 $w$ 满足 $w^2\equiv K$,其中 $K^{\frac{p-1}{2}}\equiv -1$。因为 $x^2\equiv K$ 无解,所以这里的 $w$ 类似于 $i^2=-1$ 的 $i$,并非整数。同时,由 $w$ 构成的数 $a+bw$ 也不满足欧拉定理等,需要新的结论。
**结论四**:对于奇素数 $p$,有 $(a+bw)^{p+1}\equiv a^2-b^2K\pmod p$。
以下证明:由二项式定理可得:
$$(a+bw)^{p+1}=\sum_{i=0}^{p+1}C_{p+1}^ia^{p+1-i}(bw)^i$$
聚焦于组合数 $C_{p+1}^i\equiv\frac{p!}{i!(p+1-i)!}$,可以发现仅在 $i=0,1,p,p+1$ 这四个值时组合数模 $p$ 后不为 $0$。因此原式等于:
$$C_{p+1}^0a^{p+1-0}(bw)^0+C_{p+1}^1a^{p+1-1}(bw)^1+C_{p+1}^{p}a^{p+1-p}(bw)^p+C_{p+1}^{p+1}a^{p+1-p-1}(bw)^{p+1}$$
即 $a^{p+1}+a^{p}bw+ab^pw^p+b^{p+1}w^{p+1}\equiv a^2+abw+abw^p+b^2w^{p+1}$。
$\because w^2\equiv K,K^{\frac{p-1}{2}}\equiv -1$
$\therefore (w^2)^{\frac{p-1}{2}}\equiv w^{p-1}\equiv -1$
代入原式得:$a^2-b^2w^2\equiv a^2-b^2K$,证毕。
接下来,设 $w^2\equiv 5$,则通项公式可写成:$f_i=\dfrac{(\frac{p+1}{2}+\frac{p+1}{2}w)^i-(\frac{p+1}{2}-\frac{p+1}{2}w)^i}{w}$。
先看 $\frac{p+1}{2}+\frac{p+1}{2}w$,由结论四得:$(\frac{p+1}{2}+\frac{p+1}{2}w)^{p+1}\equiv (\frac{p+1}{2})^2-5(\frac{p+1}{2})^2\equiv -1$。
所以 $(\frac{p+1}{2}+\frac{p+1}{2}w)^{2p+2}\equiv 1$,同理 $(\frac{p+1}{2}-\frac{p+1}{2}w)^{2p+2}\equiv 1$。
因此 $f_{i+2p+2}\equiv f_i$,取 $\pi(p)=2p+2$。
------------
### Part 2:
接下来,讨论模数 $P$ 是**某个质数的幂**的情况,记 $P=p^k$。
**注**:Part 2 的证明参考了部分资料(链接见文末)。
**结论五**:对于质数 $p$,已知 $a\equiv 1\pmod p$,则 $a^{p^k}\equiv 1\pmod {p^{k+1}}$。
以下证明:考虑数学归纳法,假设 $k$ 时等式成立,证明 $k+1$ 时等式也成立。
设 $a^{p^{k}}=sp^{k+1}+1$,那么 $a^{p^{k+1}}=(a^{p^k})^p=(sp^{k+1}+1)^p$。
由二项式定理:
$$(sp^{k+1}+1)^p=\sum_{i=0}^pC_p^i(sp^{k+1})^i$$
当 $i\ge 2$ 时,$p^{(k+1)i}\bmod p^{k+2}=0$,只需算出 $i=0,1$ 的值。代入得 $1+sp^{k+2}\equiv 1\pmod {p^{k+2}}$,证毕。
方便起见,记 $\frac{1+\sqrt 5}{2}=u,\frac{1-\sqrt 5}{2}=v$。
**结论六**:对于质数 $p$,有 $u^{\pi(p)}\equiv v^{\pi(p)}\equiv 1\pmod p$。
$\because f_{\pi(p)}\equiv 0\pmod p$
$\therefore u^{\pi(p)}\equiv v^{\pi(p)}$
$\because f_{\pi(p)+1}\equiv 1$
$\therefore {u^{\pi(p)}u-v^{\pi(p)}v}\equiv \sqrt 5$
代入 $u^{\pi(p)}\equiv v^{\pi(p)}$ 化简得:$u^{\pi(p)}\equiv v^{\pi(p)}\equiv 1$,证毕。
结合结论五和结论六可得:$u^{\pi(p)p^{k-1}}\equiv v^{\pi(p)p^{k-1}}\equiv 1\pmod {p^k}$。
因此 $f_{\pi(p)p^{k-1}}\equiv \frac{1}{\sqrt 5}(u^{\pi(p)p^{k-1}}-v^{\pi(p)p^{k-1}})\equiv 0\pmod {p^k}$。
且 $f_{1+\pi(p)p^{k-1}}\equiv \frac{1}{\sqrt 5}(u^{\pi(p)p^{k-1}}u-v^{\pi(p)p^{k-1}}v)\equiv \frac{u-v}{\sqrt 5}\equiv 1\pmod {p^k}$。
也就是说:$f_{i+\pi(p)p^{k-1}}\equiv f_i\pmod {p^k}$,取 $\pi(p^k)=\pi(p)p^{k-1}$。
------------
### Part 3:
最后一步了,也是最简单的情况。
**结论七**:设模数 $P$ 的质因数分解式为 $\prod p^k$,则可取 $\pi(P)=\operatorname{lcm}(\pi(p_i^{k_i}))$。
以下证明:
$\because f_{\pi(P)}\equiv 0\pmod{p_i^{k_i}},p_i^{k_i}\mid f_{\pi(P)}$
$\therefore {\pi(p_i^{k_i})}\mid {\pi(P)}$
取 $\pi(P)$ 为 $\pi(p_i^{k_i})$ 的最小公倍数即可,证毕。
------------
## 代码实现
------------
回到原题,开始整理思路。
1. 对 $P$ 进行质因数分解,时间复杂度 $O(\sqrt P)$;
2. 对于 $P$ 的每个质因数分解 $p_i^{k_i}$,求出对应的周期长 $\pi(p_i^{k_i})$;
3. 求出每个周期的最小公倍数,作为 $\pi(P)$ 的值;
4. 把读入的 $n$ 模以 $\pi(P)$,然后矩阵快速幂解决。
就这样,代码在此:
```cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
ll FastPow(ll a, ll b, ll mod){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
b >>= 1, a = a * a % mod;
}
return res;
}
ll FastPowNoMod(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a;
b >>= 1, a = a * a;
}
return res;
}
const int Mx = 3e7 + 10;
int Fac[30], Tim[30], tot;
char N[Mx];
void Divide(int p){ // 分解质因数
for(int i = 2; i * i <= p; ++i) if(p % i == 0){
Fac[++tot] = i;
while(p % i == 0) p /= i, ++Tim[tot];
}
if(p != 1) Fac[++tot] = p, Tim[tot] = 1;
}
ll PrimeLoop(ll p){ // 求 pi(p)
if(p == 2) return 3;
if(p == 5) return 20;
if(FastPow(5, (p - 1) >> 1, p) == 1) return p - 1;
return 2 * p + 2;
}
ll PrimePow(int id){ return FastPowNoMod(Fac[id], Tim[id] - 1) * PrimeLoop(Fac[id]);} // 求 pi(p^k)
ll Gcd(ll a, ll b){ return b == 0 ? a : Gcd(b, a % b);}
ll GetLoop(){ // 求 pi(P)
ll res = PrimePow(1), temp;
for(int i = 2; i <= tot; ++i) temp = PrimePow(i), res = res / Gcd(res, temp) * temp;
return res;
}
int P;
struct Matrix{
ll val[3][3];
Matrix operator * (const Matrix& a) const {
Matrix Res = (*this);
for(int i = 1; i <= 2; ++i) for(int j = 1; j <= 2; ++j)
Res.val[i][j] = (val[i][1] * a.val[1][j] % P + val[i][2] * a.val[2][j] % P) % P;
return Res;
}
Matrix operator *= (const Matrix& a) { return (*this) = a * (*this);}
Matrix operator ^ (ll k) const {
Matrix Res, Temp = (*this);
Res.val[1][1] = Res.val[2][2] = 1, Res.val[1][2] = Res.val[2][1] = 0;
while(k){
if(k & 1) Res *= Temp;
k >>= 1, Temp *= Temp;
}
return Res;
}
} Fibo, Cell;
void Solve(ll n){
if(n == 0){
printf("0");
return;
}
Fibo.val[1][1] = 0, Fibo.val[2][1] = Fibo.val[1][2] = Fibo.val[2][2] = 1;
Cell.val[1][1] = 1, Cell.val[1][2] = 1;
Matrix Res = Cell * (Fibo ^ (n - 1));
printf("%lld", Res.val[1][1] % P);
}
int main(){
scanf(" %s %d", N, &P);
if(P == 1){
printf("0");
return 0;
}
Divide(P);
ll Loop = GetLoop(), n = 0;
int Len = strlen(N);
for(int i = 0; i < Len; ++i) n = (n * 10 + (N[i] ^ 48)) % Loop;
Solve(n);
return 0;
}
```
------------
#### 参考资料:
- [Pisano Period](https://www.cnblogs.com/cjoierShiina-Mashiro/p/11385287.html)
- [The Period of the Fibonacci Sequence Modulo j
](https://gradprogram.math.arizona.edu/~ura-reports/071/Campbell.Charles/Final.pdf)