🧮 Реализация на Дълги Числа (BigInt)
В C++ най-големият целочислен тип unsigned long long поддържа числа до \(\approx 1.8 \times 10^{19}\). Много задачи обаче изискват работа с числа със стотици или хиляди цифри. За разлика от Python и Java, C++ няма вградена поддръжка за BigInteger, така че трябва да си я напишем сами.
1. Стратегия за съхранение
Най-ефективният начин е да съхраняваме числото в std::vector<int>, но не цифра по цифра (база 10), а групирайки ги, например по 9 цифри (база \(10^9\)).
* База \(10^9\) се побира в int.
* Умножението на две "цифри" (до \(10^{18}\)) се побира в long long.
* Намалява броя на операциите 9 пъти спрямо база 10.
Векторът пази цифрите в обратен ред (най-младшите са на индекс 0), за да може лесно да се разширява.
2. Пълна имплементация (Template)
Ето една компактна, но функционална структура, която можете да използвате в състезания:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iomanip>
using namespace std;
const int BASE = 1e9;
struct BigInt {
vector<int> digits;
// Конструктори
BigInt() {}
BigInt(long long v) {
if (v == 0) digits.push_back(0);
while (v > 0) {
digits.push_back(v % BASE);
v /= BASE;
}
}
BigInt(string s) {
for (int i = (int)s.length(); i > 0; i -= 9) {
if (i < 9) digits.push_back(stoi(s.substr(0, i)));
else digits.push_back(stoi(s.substr(i - 9, 9)));
}
trim();
}
void trim() {
while (digits.size() > 1 && digits.back() == 0)
digits.pop_back();
}
// Оператори за сравнение
bool operator<(const BigInt &other) const {
if (digits.size() != other.digits.size())
return digits.size() < other.digits.size();
for (int i = digits.size() - 1; i >= 0; i--)
if (digits[i] != other.digits[i])
return digits[i] < other.digits[i];
return false;
}
bool operator>(const BigInt &other) const { return other < *this; }
bool operator==(const BigInt &other) const { return !(*this < other) && !(other < *this); }
// Събиране
BigInt operator+(const BigInt &other) const {
BigInt res = *this;
int carry = 0;
for (size_t i = 0; i < max(res.digits.size(), other.digits.size()) || carry; ++i) {
if (i == res.digits.size()) res.digits.push_back(0);
long long current = res.digits[i] + carry + (i < other.digits.size() ? other.digits[i] : 0);
res.digits[i] = current % BASE;
carry = current / BASE;
}
return res;
}
// Умножение с късо число (int)
BigInt operator*(int v) const {
if (v == 0) return BigInt(0);
BigInt res = *this;
long long carry = 0;
for (size_t i = 0; i < res.digits.size() || carry; ++i) {
if (i == res.digits.size()) res.digits.push_back(0);
long long cur = carry + (long long)res.digits[i] * v;
res.digits[i] = cur % BASE;
carry = cur / BASE;
}
res.trim();
return res;
}
// Умножение с BigInt (School method O(N*M))
BigInt operator*(const BigInt &other) const {
BigInt res;
res.digits.resize(digits.size() + other.digits.size(), 0);
for (size_t i = 0; i < digits.size(); ++i) {
long long carry = 0;
for (size_t j = 0; j < other.digits.size() || carry; ++j) {
long long cur = res.digits[i+j] + (long long)digits[i] * (j < other.digits.size() ? other.digits[j] : 0) + carry;
res.digits[i+j] = cur % BASE;
carry = cur / BASE;
}
}
res.trim();
return res;
}
// Изход
friend ostream& operator<<(ostream &os, const BigInt &bi) {
if (bi.digits.empty()) { os << 0; return os; }
os << bi.digits.back();
for (int i = bi.digits.size() - 2; i >= 0; i--) {
os << setfill('0') << setw(9) << bi.digits[i];
}
return os;
}
};
3. Python или Java?
Ако задачата позволява използването на други езици и ограничението за време не е твърде строго (Python е по-бавен), често е по-лесно просто да превключите езика.
В Python int автоматично поддържа произволна точност.
Python пример:
4. Оптимизации
- Карацуба (Karatsuba): За много големи числа (напр. над 2000-3000 цифри) стандартното умножение \(O(N^2)\) е бавно. Алгоритъмът на Карацуба работи за \(O(N^{\log_2 3}) \approx O(N^{1.58})\).
- FFT (Fast Fourier Transform): За екстремно големи числа (\(10^5\) цифри), умножението става за \(O(N \log N)\) (виж Тема 44).
5. Задачи
- UVa 10106 - Product: Просто умножение на две големи числа.
- SPOJ JULKA: Събиране и изваждане / деление на 2.
- Codeforces 1042D: Може да се наложи BigInt за междинни сметки.
6. Деление на дълги числа
Делението е най-сложната операция при BigInt. Има два основни подхода:
6.1. Двоично търсене
Ако делим \(A\) на \(B\) и искаме частното \(Q\), можем да използваме двоично търсене върху отговора: * Търсим най-голямото \(Q\), за което \(Q \times B \le A\). * Остатъкът е \(R = A - Q \times B\).
Сложност: \(O((\log A) \cdot M)\), където \(M\) е сложността на умножението.
6.2. "School Division" метод
Подобно на дългото деление, което учим на училище, можем да делим "цифра" по "цифра" (в нашата база \(10^9\)):
BigInt divide(const BigInt& dividend, int divisor, int& remainder) {
BigInt result;
long long carry = 0;
for (int i = dividend.digits.size() - 1; i >= 0; i--) {
long long current = carry * BASE + dividend.digits[i];
result.digits.push_back(current / divisor);
carry = current % divisor;
}
remainder = carry;
reverse(result.digits.begin(), result.digits.end());
result.trim();
return result;
}
7. Факториел на големи числа
Класическа задача: Изчислете \(N!\) за \(N \le 1000\).
BigInt factorial(int n) {
BigInt result(1);
for (int i = 2; i <= n; i++) {
result = result * i;
}
return result;
}
Например, \(100!\) има 158 цифри и е извън обхвата на всички вградени типове.
8. GCD на дълги числа
Алгоритъмът на Евклид работи и за BigInt, но ако едното число е малко (int), има по-бърз метод:
int gcd_bigint_int(BigInt a, int b) {
// Евклид с mod операция
while (b != 0) {
int remainder;
a = divide(a, b, remainder);
swap(a, BigInt(b));
b = remainder;
}
return a.digits[0]; // Резултатът ще е малко число
}
9. Степенуване на BigInt
За \(A^B \pmod{M}\), ако \(A\) е BigInt, а \(B\) и \(M\) са обикновени числа:
BigInt powerMod(BigInt base, int exp, int mod) {
BigInt result(1);
while (exp > 0) {
if (exp % 2 == 1) {
result = (result * base); // После вземете модул
}
base = (base * base); // После вземете модул
exp /= 2;
}
return result;
}
mod да е BigInt.
10. Алтернативи и оптимизации
10.1. Използване на Python
Ако състезанието позволява:
a = int(input())
b = int(input())
print(a ** b) # Python автоматично работи с произволно големи числа
10.2. Библиотеки в C++
- GMP (GNU Multiple Precision Library): Изключително бърза и оптимизирана библиотека за произволно големи числа.
- Обаче, повечето състезания НЕ позволяват външни библиотеки.
🏁 Заключение
Имплементацията на BigInt е важно умение за състезатели. Добре е да имате подготвен шаблон, който да копирате при нужда. Ключовите елементи са:
* Избор на подходяща база (\(10^9\) е оптимална).
* Коректно управление на carry при аритметични операции.
* trim() за премахване на водещи нули.
* Тестване с екстремни случаи (0, много големи числа, еднакви числа).
Въпреки че изглежда сложно, след като го напишете веднъж, можете да го използвате многократно!