📈 Полиноми и Пълна реализация на Дълги числа (BigInt)
🧮 Дълги числа (BigInt) - Детайлна реализация
Стандартните типове данни (long long) са ограничени до \(\approx 9 \cdot 10^{18}\). В задачи с по-големи числа (напр. 100-цифрени) трябва да имплементираме собствена структура.
Избор на база
Вместо да пазим по една цифра (база 10), е много по-ефективно да пазим по 9 цифри (база \(10^9\)) в един int. Това намалява паметта и броя операции 9 пъти.
- База \(10^9\) се побира в int, а произведението на две "цифри" (\(10^{18}\)) се побира в long long.
Пълна структура BigInt
#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();
}
}
// Оператор за извеждане
friend ostream& operator<<(ostream& os, const BigInt& bi) {
if (bi.digits.empty()) { os << 0; return os; }
os << bi.digits.back();
for (int i = (int)bi.digits.size() - 2; i >= 0; i--) {
os << setfill('0') << setw(9) << bi.digits[i];
}
return os;
}
// Сравнение
bool operator<(const BigInt& other) const {
if (digits.size() != other.digits.size())
return digits.size() < other.digits.size();
for (int i = (int)digits.size() - 1; i >= 0; i--)
if (digits[i] != other.digits[i])
return digits[i] < other.digits[i];
return false;
}
// Събиране
BigInt operator+(const BigInt& other) const {
BigInt res;
int carry = 0;
for (size_t i = 0; i < max(digits.size(), other.digits.size()) || carry; ++i) {
int current = carry + (i < digits.size() ? digits[i] : 0) +
(i < other.digits.size() ? other.digits[i] : 0);
res.digits.push_back(current % BASE);
carry = current / BASE;
}
return res;
}
// Изваждане (приемаме, че *this >= other)
BigInt operator-(const BigInt& other) const {
BigInt res;
int carry = 0;
for (size_t i = 0; i < digits.size(); ++i) {
int current = digits[i] - carry - (i < other.digits.size() ? other.digits[i] : 0);
if (current < 0) {
current += BASE;
carry = 1;
} else {
carry = 0;
}
res.digits.push_back(current);
}
res.trim();
return res;
}
// Умножение с късо число (long long)
BigInt operator*(long long v) const {
BigInt res;
long long carry = 0;
for (int d : digits) {
long long cur = d * v + carry;
res.digits.push_back(cur % BASE);
carry = cur / BASE;
}
while (carry) {
res.digits.push_back(carry % BASE);
carry /= BASE;
}
res.trim();
return res;
}
// Умножение на две BigInt числа
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] + digits[i] * 1LL * (j < other.digits.size() ? other.digits[j] : 0) + carry;
res.digits[i + j] = cur % BASE;
carry = cur / BASE;
}
}
res.trim();
return res;
}
};
📈 Полиноми
Полиномите са обобщение на числата. Ако число е полином, оценен в точка \(x=10\) (за десетична система), то операциите са аналогични.
Схема на Хорнер - Оптимизация
Схемата на Хорнер намалява броя на умноженията при пресмятане на \(P(x)\). Вместо \(a_n x^n + \dots + a_0\), записваме: $\((\dots((a_n x + a_{n-1})x + a_{n-2})x + \dots)x + a_0\)$ Така са нужни само \(N\) умножения и \(N\) събирания.
Умножение на полиноми
- Наивен метод: \(O(N^2)\) - идентичен с умножението на BigInt по-горе.
- Бързо умножение (FFT): \(O(N \log N)\). Преобразува полиномите в стойности (Point-Value form), умножава ги линейно и ги връща обратно. (Виж Тема 44).
Интерполация на Лагранж
Ако имаме \(N+1\) точки \((x_i, y_i)\), съществува единствен полином от степен \(\le N\), който минава през тях. $\(P(x) = \sum_{j=0}^{N} y_j L_j(x), \quad \text{където } L_j(x) = \prod_{i \neq j} \frac{x - x_i}{x_j - x_i}\)$
Това е полезно за възстановяване на функция по нейните стойности, особено в задачи за намиране на "следващия член на редицата".
🏁 Заключение
Имплементацията на BigInt е често срещана подзадача. Добре е да имате подготвен и тестван шаблон ("черна кутия"), който да копирате по време на състезание. Внимавайте с "trim" функцията за премахване на водещите нули, тъй като те могат да счупят логиката на сравнението или извеждането.
11. Работа с полиноми - практически примери
11.1. Оценяване на полином с Хорнер
// P(x) = a[0] + a[1]*x + a[2]*x^2 + ... + a[n]*x^n
double horner(vector<double>& a, double x) {
double result = 0;
for (int i = a.size() - 1; i >= 0; i--) {
result = result * x + a[i];
}
return result;
}
11.2. Събиране на полиноми
vector<int> addPolynomials(vector<int>& p1, vector<int>& p2) {
int maxDegree = max(p1.size(), p2.size());
vector<int> result(maxDegree, 0);
for (int i = 0; i < p1.size(); i++) result[i] += p1[i];
for (int i = 0; i < p2.size(); i++) result[i] += p2[i];
return result;
}
11.3. Умножение на полиноми (наивен метод)
vector<int> multiplyPolynomials(vector<int>& p1, vector<int>& p2) {
vector<int> result(p1.size() + p2.size() - 1, 0);
for (int i = 0; i < p1.size(); i++) {
for (int j = 0; j < p2.size(); j++) {
result[i + j] += p1[i] * p2[j];
}
}
return result;
}
12. Интерполация - практическо приложение
Задача: Намиране на следващ член в редица
Дадени са първите \(N+1\) членове на редица. Намерете \((N+2)\)-рия член.
Решение: Ако редицата се описва от полином от степен \(\le N\), можем да използваме интерполация на Лагранж.
// Дадени точки (0, y0), (1, y1), ..., (n, yn)
// Намираме стойността на полинома в точка x
double lagrangeInterpolation(vector<double>& y, double x) {
int n = y.size();
double result = 0;
for (int j = 0; j < n; j++) {
double term = y[j];
for (int i = 0; i < n; i++) {
if (i != j) {
term *= (x - i) / (j - i);
}
}
result += term;
}
return result;
}
Пример: Ако имаме \(y = [1, 4, 9, 16, 25]\) (квадратите на числата), това е полином от степен 2. Можем да намерим следващия член за \(x=5\): \(P(5) = 36\).
13. Връзка с динамичното програмиране
Полиномите често се появяват в DP задачи, особено при: * Generating functions (производящи функции). * Комбинаторика (биномиални коефициенти, Catalan числа).
Например, броят на начините да разменим монети може да се представи като коефициенти в произведение на полиноми.
14. Задачи за упражнение
- Project Euler 101: Optimum Polynomial (интерполация).
- Codeforces 528D: Fuzzy Search (може да се реши с FFT умножение на полиноми).
- SPOJ POLYMUL: Директно умножение на полиноми.
- UVa 10105: Polynomial Coefficients (комбинаторика с полиноми).
🏁 Финални съвети
- За BigInt задачи винаги тествайте с екстремни случаи (0, много големи числа, еднакви числа).
- При полиноми внимавайте с overflow - използвайте
long longили модулна аритметика. - Схемата на Хорнер е елегантна и ефективна - използвайте я винаги, когато оценявате полином.
- За конкурси с позволени външни библиотеки (GMP, NTL) научете се да ги използвате - спестяват време.
Владеенето на работата с произволно големи числа и полиноми отваря вратата към много напреднали алгоритми като FFT, NTT и различни криптографски приложения!