Məlumdur ki, Fibonacci ədədləri üçün aşağıdakı bərabərlik doğrudur:
Bu o deməkdir ki, məsələnin həlli 10^8 modula uyğun -ci Fibonacci ədədinin hesablanmasına çevrilir, burada
Çünki n, m \le 10^{18}, buna görə də k ≤ 10^{18}. Nəticədə, F_k~mod~10^8 dəyərini O(log_2k) müddətində hesablamaq lazımdır.
Teorem. Fibonacci ədədlərini sürətlə hesablamaq üçün matris üsulundan istifadə etmək əlverişlidir. Məlumdur ki:
İnduksiya Bazası. k = 1 üçün alırıq:
Bu doğrudur, çünki F_0 = 0, F_1 = 1, F_2 = 1.
İnduksiya Addımı. Formulun üçün doğru olduğunu güman edək. O zaman k + 1 üçün:
Nəticə etibarilə, formul bütün k \ge 1 üçün doğrudur.
Yerdə qalır matrisin əmsalına sürətli yüksəltmə üsulu ilə O(log_2k) müddətində əmələ gətirilməsi.
Misal
Fibonacci ədədləri belə görünür:
Matris üsulu ilə Fibonacci ədədlərini hesablayacağıq:
Algoritmin təcəssümü – operatorların aşırı yüklenməsi
Hesablama işlərini modula ilə aparmaq üçün bir sabit təyin edək.
#define MOD 100000000gcd funksiyası iki ədəd və arasında ən böyük ortaq dəyəri hesablayır.
long long gcd(long long a, long long b)
{
return (!b) ? a : gcd(b, a % b);
}Matrix matrisini elan edək və konstruktörünü təsvir edək.
class Matrix
{
public:
long long a, b, c, d;
Matrix(long long a = 1, long long b = 0,
long long c = 0, long long d = 1)
{
this->a = a; this->b = b;
this->c = c; this->d = d;
}Matrislərin vurulması operatorunu aşırı yükleyək. Bütün hesablamaları MOD = 10^8 modula uyğun aparırıq.
Matrix operator* (const Matrix &x)
{
Matrix res;
res.a = (a * x.a + b * x.c) % MOD;
res.b = (a * x.b + b * x.d) % MOD;
res.c = (c * x.a + d * x.c) % MOD;
res.d = (c * x.b + d * x.d) % MOD;
return res;
}Matrisin dərəcəsinə yüksəlməsinə aşırı yüklenmə üsulunu tətbiq edək. Algoritmin iş müddəti O(log_2n).
Matrix operator^ (long long n)
{
Matrix x(*this);
if (n == 0) return Matrix();
if (n & 1) return x * (x ^ (n - 1));
return (x * x) ^ (n/2);
}
};fib funksiyası -inci Fibonacci ədədini F_n 10^8 modula uyğun olaraq verir.
long long fib(long long n)
{
Matrix res(1,1,1,0);
res = res ^ n;
return res.b;
}Programın əsas hissəsi. Giriş məlumatlarını oxuyuruq, sonra F_{ ext{QOB}(n, m)} dəyərini hesablamaq və çıxmaq.
while(scanf("%lld %lld",&n,&m) == 2)
{
d = gcd(n,m);
printf("%lld\n",fib(d));
}Algoritmin təcəssümü – funksiyalar
Hesablama işlərini modula ilə aparmaq üçün bir sabit təyin edək.
#define MOD 100000000gcd funksiyası iki ədəd və arasında ən böyük ortaq dəyəri hesablayır.
long long gcd(long long a, long long b)
{
return (!b) ? a : gcd(b, a % b);
}Matrix strukturunu elan edək – 2 \times 2 ölçüsündə matris. Əks şərtlər üçün default olaraq vahid matris yaratılır, bu yüksəltmə zamanı əlverişlidir.
struct Matrix
{
long long a, b, c, d;
Matrix(long long a_ = 1, long long b_ = 0,
long long c_ = 0, long long d_ = 1)
: a(a_), b(b_), c(c_), d(d_) {}
};multiply funksiyası standart formul ilə iki matris 2 \times 2 vurur.
Matrix multiply(Matrix &x, Matrix &y)
{
return Matrix(
(x.a * y.a + x.b * y.c) % MOD,
(x.a * y.b + x.b * y.d) % MOD,
(x.c * y.a + x.d * y.c) % MOD,
(x.c * y.b + x.d * y.d) % MOD
);
}power funksiyası matris -in əmsalına binar üsul ilə yüksəlməsini təmin edir.
Matrix power(Matrix base, long long exp)
{
Matrix result;
while (exp > 0)
{
if (exp & 1)
result = multiply(result, base);
base = multiply(base, base);
exp >>= 1;
}
return result;
}fib funksiyası matris metodi ilə -ci Fibonacci ədədini 10^8 modula uyğun olaraq verir.
long long fib(long long n)
{
if (n == 0) return 0;
Matrix m(1, 1, 1, 0);
Matrix res = power(m, n);
return res.b; // F(n)
}Programın əsas hissəsi. Giriş məlumatlarını oxuyuruq, sonra F_{ ext{QOB}(n, m)} dəyərini hesablamaq və çıxmaq.
while (scanf("%lld %lld", &n, &m) == 2)
{
d = gcd(n, m);
printf("%lld\n", fib(d));
}Algoritmin təcəssümü - xatırlatma
Fibonacci ədədləri üçün aşağıdakı bərabərlik məlumdur:
Bunlardan birbaşa olaraq xüsusi halları çıxarıla bilər:
m = n olduqda, F_{2n} = F_n \cdot F_{n+1} + F_{n-1} \cdot F_n.
m = n + 1 olduqda, F_{2n+1} = F_{n+1} \cdot F_{n+1} + F_n \cdot F_n.
Bu formullar Fibonacci ədədlərini "böl və idarə et" metodu ilə hesablamağa imkan verir, hesablama F_n dəyərini təxminən yarıya indirən indekslərə çox asan olur.
Baza halları ayrıcayrıca işlənir:
Bu yanaşma sayəsində rekursiyanın dərinliyi log_2n-dən proporsionaldır və algoritmin iş müddəti O(log_2n)-dir.
Hesablama işlərini modula ilə aparmaq üçün bir sabit təyin edək.
#define MOD 100000000tipli dəyişənmap003e Fibonacci ədədlərini artıq hesablamaq üçün saxladırıq.
map<long long, long long> F;gcd funksiyası iki ədəd və arasında ən böyük ortaq dəyəri hesablayır.
long long gcd(long long a, long long b)
{
return (!b) ? a : gcd(b,a % b);
}fib funksiyası -ci Fibonacci ədədini 10^8 modula uyğun olaraq verir.
long long fib(long long n)
{Baza halları ayrıcayrıca işlənir.
if (n == 0) return 0;
if (n == 1) return 1;
if (n == 2) return 1;Əgər F_n dəyəri artıq əvvəllər hesablanmış və -də saxlanmışsa, bu dəyər təkrar hesablama olmadan dərhal qaytarılır.
if (F[n]) return F[n];Daha sonra üzərində parçalanma:
n = 2k + 1 — tək hal: F_n=F_k^2 + F_{k+1}^2.
n = 2k — cüt hal: F_n = F_k \cdot F_{k+1} + F_{k-1} \cdot F_k.
long long k = n / 2;Sonucu saxlayıb qaytarırıq.
if (n % 2 == 1) // n = 2*k + 1
return F[n] = (fib(k) * fib(k) + fib(k+1) * fib(k+1)) % MOD;
else // n = 2*k
return F[n] = (fib(k) * fib(k+1) + fib(k-1) * fib(k)) % MOD;
}Programın əsas hissəsi. Giriş məlumatlarını oxuyuruq, sonra F_{ ext{QOB}(n, m)} dəyərini hesablamaq və çıxmaq.
while(scanf("%lld %lld",&a,&b) == 2)
{
d = gcd(a,b);
printf("%lld\n",fib(d));
}Java təcəssümü
import java.util.*;
public class Main
{
static HashMap<Long, Long> F = new HashMap<Long, Long>();
static long MOD = 100000000;
static long gcd(long a, long b)
{
return (b == 0) ? a : gcd(b,a % b);
}
static long fib(long n)
{
if (n == 0) return 0;
if (n == 1) return 1;
if (n == 2) return 1;
if (F.containsKey(n)) return F.get(n);
long k = n / 2;
if (n % 2 == 1)
{
F.put(n, (fib(k) * fib(k) + fib(k+1) * fib(k+1)) % MOD);
return F.get(n);
}
else
{
F.put(n, (fib(k) * fib(k+1) + fib(k-1) * fib(k)) % MOD);
return F.get(n);
}
}
public static void main(String[] args)
{
Scanner con = new Scanner(System.in);
while(con.hasNext())
{
long a = con.nextLong();
long b = con.nextLong();
long d = gcd(a,b);
System.out.println(fib(d));
}
con.close();
}
}