Bài toán
Nguồn: Codeforces
Đọc hiểu
Tìm phần dư của số Fibonacci
thứ $n$ ($n <= 10^{18}$) cho $10^9 + 7$. Như vậy, mình cần tìm $F_n$ trong dãy số được định nghĩa:
$$
\begin{aligned}
F_0 & = 0\\
F_1 & = 1\\
F_i & = F_{i-1} + F_{i-2} (i >= 2)
\end{aligned}
$$
Ví dụ:
Input: 50
Output: 586268941
Lời giải
Cách 1: Quy hoạch động
Xem phương trình sai phân của dãy fibonacci là công thức truy hồi, mình có thể viết thành đoạn code sau:
int f0 = 0, f1 = 1, fn;
for(int i = 2; i <= n; i++){
fn = f0 + f1;
f0 = f1;
f1 = fn;
}
return fn;
Với độ phức tạp là $O(n)$, đoạn code này chạy ổn với $n <= 5. 10^6$. Tuy nhiên, $n$ có thể lên đến $10^{18}$ ở bài toán này nên mình cần một giải thuật tốt hơn, ít nhất phải là $O(log^2 n)$
Cách 2: Nhân ma trận
Từ phương trình sai phân của dãy fibonacci, mình có thể viết lại như sau: $$ \begin{aligned} F_{n+1} & = 1\ F_n + 1\ F_{n-1}\\ F_{n} & = 1\ F_n + 0\ F_{n-1}\\ \\ => \begin{bmatrix} F_{n+1} \\ F_n \end{bmatrix} & = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \begin{bmatrix} F_n \\ F_{n-1} \end{bmatrix} \\\\ & = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^n \begin{bmatrix} F_1 \\ F_0 \end{bmatrix} \end{aligned} $$ Có thể thấy $F_n$ sẽ bằng dòng thứ 2 của $\begin{bmatrix} 1 & 1 \\ 1 & 0\end{bmatrix}^n$ nhân với $\begin{bmatrix} F_1 \\ F_0 \end{bmatrix}$. Mình lại có $F_1 = 1, F_0 = 0$ nên $F_n$ lúc này sẽ là phần tử ở dòng 2 cột 1 của $\begin{bmatrix} 1 & 1 \\ 1 & 0\end{bmatrix}^n$.
Tính lũy thừa với n lớn
Đối với $n$ lớn, thay vì cứ nhân cơ số $n$ lần theo định nghĩa, mình sẽ bình phương cơ số, đồng thời giảm số mũ đi một nửa. Ví dụ: Thay vì tính $2^6 = 2\times2\times2\times2\times2\times2,$ mình sẽ tính $2^6 = 4^4 = 8^2$
Nói cách khác, mình sẽ cố gắng thay thế việc nhân nhiều lần một số nhỏ thành nhân vài lần các số lớn với nhau. Với cách này mình sẽ đưa độ phức tạp về $O(logn)$.
int res = 1;
while (b > 0){
if(b % 2)
res = res * a;
a = a * a;
b >>= 1
}
Hiện tại, cơ số của mình là một ma trận $2\times2$ nên mình cần khai báo struct
và thêm hàm để thuận lợi cho việc tính toán cũng như lưu trữ:
const unsigned long long mod = 1e9 + 7;
struct matrix{
unsigned long long a, b, c, d;
};
matrix multiply(matrix A, matrix B){
matrix C;
C.a = (A.a*B.a + A.b*B.c) % mod;
C.b = (A.a*B.b + A.b*B.d) % mod;
C.c = (A.c*B.a + A.d*B.c) % mod;
C.d = (A.c*B.b + A.d*B.d) % mod;
return C;
}
matrix power(matrix a, long long b){
matrix res;
//cho res là ma trận đơn vị
res.a = res.d = 1;
res.b = res.c = 0;
while (b > 0){
if(b % 2)
res = multiply(res, a);
a = multiply(a, a);
b >>= 1;
}
return res;
}
Cuối cùng, ta cho $F = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}$, kết quả cuối cùng sẽ là power(F,n).c
.
Cảm ơn bạn vì đã đọc. Chúc bạn có một năm mới tràn đầy niềm vui và hạnh phúc =w=.