[Вся длинная арифметика]
В завершении обзора задач на длинную арифметику рассмотрим эту интересную задачу. Часто при работе с длинными числами бывает нужно получить не все число целиком, а только несколько последних цифр. В таких задачах даже порой можно обойтись вообще без длинной арифметики.
По условию задачи 1<=N<=1e9 и 1<=P<=100.
суббота, 11 сентября 2010 г.
N^N mod 10^P
Квадратный корень из длинного числа
[Вся длинная арифметика]
Эта задача далась мне тяжелее всех. Наверное потому, что я пытался зачесть ее на acm.sgu.ru и acmp.ru, где ограничения на количество разрядов в числе 1000 и 3000 соответственно. Для начала зачтем эту задачу на informatics.mccme.ru, где ограничение всего 100.
Здесь можно пойти двумя путями:
воскресенье, 5 сентября 2010 г.
Длинное - короткое
До того момента, пока не столкнулся с извлечением квадратного корня из длинного числа, был уверен, что реализация этой операции не будет иметь никакого практического значения. Но я ошибался.
Приведенная реализация практически идентична с реализацией операции “длинное + короткое”:
- BigInt operator - (const BigInt &a, const int &b)
- {
- BigInt res = a;
- int pos = 0;
- res.digits[0] -= b;
- while (res.digits[pos] < 0)
- {
- res.digits[pos+1] --;
- res.digits[pos++] +=osn;
- }
- if (res.amount && !res.digits[res.amount-1])
- res.amount--;
- return res;
- }
* This source code was highlighted with Source Code Highlighter.
Факториал короткого числа
Тип int может поместить в себя не больше 12!.А как же нам быть если нужно найти 100! ? Самое время обратиться к длинной арифметике.
Сама реализация не представляет для нас ничего нового:
- BigInt fact(int n)
- {
- BigInt res(1);
- for (int i=2;i<=n;i++)
- res = res * i;
- return res;
- }
* This source code was highlighted with Source Code Highlighter. По условию задачи n<=3000. В качестве osn лучше выбрать 10000, т.к. в таком случае мы сможем обойтись умножением длинного на короткое.
понедельник, 30 августа 2010 г.
Длинное^короткое (возведение в степень)
Данную операцию можно решить лобовым перемножением исходного числа, а можно воспользоваться быстрым возведением в степень:
- BigInt pow(const BigInt &a, const int &N)
- {
- BigInt res(1),cur = a;
- int n = N;
- while (n)
- {
- if (n&1)
- res = res * cur;
- cur = cur * cur;
- n>>=1;
- }
- return res;
- }
* This source code was highlighted with Source Code Highlighter. Стоит обратить внимание на то, что количество разрядов у результирующего числа очень быстро растет, поэтому нужно грамотно подбирать размерность массива digits в структуре BigInt.
воскресенье, 29 августа 2010 г.
Длинное DIV длинное & Длинное MOD длинное
[Вся длинная арифметика]
Пришло время познакомиться с самой “страшной” операцией на длинную арифметику. Первый раз я ее реализовал на Accepted 31 июля 2008. До этого момента мне было даже страшно подумать над ее реализацией. Но как оказалось не так уж это деление и страшно, как его малюют. Мы не будем изобретать велосипед, а просто грамотно, максимально понятно и быстро реализуем деление столбиком. Рассмотрим нижепредставленный рисунок.
суббота, 21 августа 2010 г.
Длинное DIV короткое & Длинное MOD короткое
[Вся длинная арифметика]
При упоминании операции деления на длинных числах молодые олимпиадники резко меняются в лице, меняя веселые гримасы на хмурые лица. Скорее всего причиной тому являются разговоры более опытных товарищей, когда последние в красках обсуждают деление длинного на длинное. Об этой операции мы поговорим чуть позже. А пока спешу успокоить, т.к. деление длинного на короткое не сложнее, чем умножение длинного на короткое):
Длинное * длинное
Итак мы подошли к первой операции, реализацию которой можно считать не такой тривиальной, как в предыдущих случаях:
- BigInt operator * (const BigInt &a, const BigInt &b)
- {
- BigInt res;
- for (int i=0;i<a.amount;i++)
- {
- int r = 0;
- for (int j=0;j<b.amount | r;j++)
- {
- res.digits[i+j] += a.digits[i] * b.digits[j] + r;
- r = res.digits[i+j] / osn;
- res.digits[i+j] -= r*osn;
- }
- }
- int pos = a.amount + b.amount;
- while (pos>0 && !res.digits[pos])
- pos--;
- res.amount = pos + 1;
- return res;
- }
* This source code was highlighted with Source Code Highlighter. Обратим внимание на строчку 11. Т.к. алгоритм квадратичный, то избежание использования операции взятия остатка от деления(%) дает прирост в скорости, более ощутимый, чем для линейных алгоритмов.
Длинное * короткое
Умножение длинного числа на короткое является частным случаем умножения длинного на длинное. Но для того, чтобы данная операция работала быстрее общего случая, нужно позаботиться об индивидуальной реализации этой операции:
- BigInt operator * (const BigInt &a, const int &n)
- {
- BigInt res;
- res.amount = a.amount;
-
- int r = 0;
- for (int i=0;i<res.amount | r;i++)
- {
- res.digits[i] = a.digits[i] * n + r;
- r = res.digits[i]/osn;
- res.digits[i] -= r*osn;
- }
- if (res.digits[res.amount])
- res.amount++;
-
- return res;
- }
* This source code was highlighted with Source Code Highlighter. Как видно алгоритм линейный. Для ускорения операций в строке 11 мы избежали использования операции взятия остатка от деления(%).
Длинное – длинное(знаковое вычитание)
Рассмотрим случай знакового вычитания, когда вычитаемое может быть больше уменьшаемого.
Если разрабатывать универсальную библиотеку для работы с длинными числами, то конечно нужно предусмотреть логический признак: положительное число или отрицательное. Это повлечет за собой переделку всех операций.
Имея в наличии реализации всех операций в неотрицательных числах можно реализовать их и с учетом знака.
У меня же стоит цель зачесть задачу на сервере проверки).
Ниже приведена логика работы программы на основе ранее разобранных операций сравнения и беззнакового вычитания:
- bool isMinus = false;
- if (a < b)
- {
- c = b - a;
- isMinus = true;
- }
- else
- c = a - b;
- if (isMinus) cout<<"-";
- c.output();
* This source code was highlighted with Source Code Highlighter.
пятница, 20 августа 2010 г.
Длинное – длинное (беззнаковое вычитание)
Рассмотрим случай беззнакового вычитания, когда вычитаемое не превосходит уменьшаемого(A-B, A>=B):
- BigInt operator - (const BigInt &a, const BigInt &b)
- {
- BigInt res = a;
- int r = 0;
- for (int i = 0;i<res.amount;i++)
- {
- res.digits[i] -= b.digits[i] + r;
- if (res.digits[i]<0)
- {
- res.digits[i]+=osn;
- res.digits[i+1]--;
- }
- }
- int pos = res.amount;
- while (pos && !res.digits[pos])
- pos--;
- res.amount = pos+1;
- return res;
- }
* This source code was highlighted with Source Code Highlighter. В реализации данной операции необходимо корректно учесть случай, когда a=b.
P.S: В первой версии выводились лишние ведущие нули. Эту ошибку обнаружил Privalov, за что выражаю ему благодарность.
среда, 18 августа 2010 г.
Сравнение длинных чисел
Чтобы сравнить длинные числа необходимо и достаточно определить три операции: “больше”, “меньше” и “проверка на равенство”. Отметим важный факт: для реализации задуманного можно реализовать только только одну операцию (или “больше” или “меньше”). Остальные можно выразить из нее.
Длинное + короткое
Более распространенной операцией сложения является операция “Длинное + длинное”. Если имеем дело с частным случаем, когда одно из слагаемых является длинным числом, а второе строго меньше основания системы счисления (<osn), т.е. является цифрой, то в целях оптимизации и ускорения работы программы следует подумать над более быстрой реализацией.
Представляю свою реализацию:
- BigInt operator + (const BigInt &a, const int &b)
- {
- BigInt res = a;
- int pos = 0;
- res.digits[0] += b;
- while (res.digits[pos]>=osn)
- {
- res.digits[pos+1]++;
- res.digits[pos++]-=osn;
- }
- if (res.digits[res.amount])
- res.amount++;
- return res;
- }
* This source code was highlighted with Source Code Highlighter. Практически идентичную реализацию имеет и операция “длинное – короткое”.
вторник, 17 августа 2010 г.
Вывод длинных чисел
[Вся длинная арифметика]
Вывод длинных чисел тесно связан с вводом). Опять же рассмотрим два случая для osn = 10 и для osn = 10000.
Первый случай:
- void output()
- {
- for (int i=amount-1;i>=0;i--)
- cout<<digits[i];
- }
* This source code was highlighted with Source Code Highlighter. Второй случай:
- int len = 4;
- const char * digitFormat = "%.4d";
- ...
- void output()
- {
- printf("%d",digits[amount-1]);
- for (int i= amount-2;i>=0;i--)
- printf(digitFormat,digits[i]);
- }
* This source code was highlighted with Source Code Highlighter. Отметим важный факт во втором случае. Дело в том, что цифра при osn = 10000 может оказаться не четырехзначной. В таком случае нужно побеспокоиться, чтобы она выводилась с лидирующими нулями. Это не касается первой цифры, поэтому ее вывод реализован отдельно
Ввод длинных чисел
Рассмотрим два случая считывания длинных чисел. Первый случай(базовый), при котором osn = 10. Во втором, в качестве osn выберем одну из степеней десятки. Забегая вперед отмечу факт, что эту степень нужно выбирать очень аккуратно, чтобы избежать переполнения при реализации операций. Так например, если цифра длинного числа хранится в int’е и мы реализуем операцию плюс, то максимально возможная степень десятки у osn будет 9. (В умных книжках обычно после подобных фраз пишут: “Почему?”). Если же реализуем операцию умножения длинного на длинное, то максимальный osn = 1e4. С учетом всего вышесказанного давайте ознакомимся с возможной реализацией первого случая:
воскресенье, 18 июля 2010 г.
Длинное + длинное
[Вся длинная арифметика]
Операция сложения двух длинных чисел:
- BigInt operator + (const BigInt &a, const BigInt &b)
- {
- BigInt res;
- res.amount = max(a.amount,b.amount);
- int r = 0;
- for (int i=0;i<res.amount | r;i++)
- {
- res.digits[i] = a.digits[i] + b.digits[i] + r;
- if (res.digits[i]>=osn)
- {
- res.digits[i]-=osn;
- r = 1;
- }
- else
- r = 0;
- }
- if (res.digits[res.amount])
- res.amount++;
-
- return res;
- }
* This source code was highlighted with Source Code Highlighter. В данной реализации отсутствует операция взятия остатка от деления. Вместо этого используется вычитание значения основания системы счисления в строке 11.
P.S: Данная реализация появилась на основе статьи Виталия Гольдштейна с учетом оформления длинных чисел команды HotFinnKeys.
четверг, 27 мая 2010 г.
Длинная арифметика
Теория:
* Первая глава книги Окулова “Программирование в алгоритмах”.
* Раздел 4 Дистанционная подготовка по информатике
* e-maxx
Практика: Дистанционная подготовка
Условные обозначения:
* osn – основание системы счисления
* A,B – “длинные” числа
* n – “короткое”, 0 <= n < osn
Предисловие
Основные операции:
* Ввод
* Вывод
* A + n
* Сравнение
* A + B
* A – B (A >= B)
* A – B (со знаком)
* A * n
* A * B
* A / n, A % n
* A / B, A % B
Дополнительные операции:
* A^n
* n!
* sqrt(A)
* N^N mod 10^P
Экзотика:
* A – n