Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритмы C++

.pdf
Скачиваний:
690
Добавлен:
15.03.2016
Размер:
6 Mб
Скачать

с умножением.

Виды дробной длинной арифметики

Операции над дробными числами встречаются гораздо реже, и работать с огромными дробными числами значительно сложнее, поэтому в олимпиадах встречается только специфическое подмножество дробной длинной арифметики.

Длинная арифметика в несократимых дробях

Число представляется в виде несократимой дроби , где и — целые числа. Тогда все операции над дробными числами нетрудно свести к операциям над числителями и знаменателями этих дробей.

Обычно при этом для хранения числителя и знаменателя приходится также использовать длинную арифметику, но, впрочем, самый простой её вид — Классическая длинная арифметика, хотя иногда достаточно применения для них 64-битного встроенного типа.

Выделение позиции плавающей точки в отдельный тип

Иногда в задаче требуется производить расчёты с очень большими либо очень маленькими числами, но при этом не допускать их переполнения. Встроенный 8-байтовый тип double, как известно, допускает значения экспоненты в диапазоне , чего иногда может оказаться недостаточно.

Приём, собственно, очень простой — вводится ещё одна целочисленная переменная, отвечающая за экспоненту, а

после выполнения каждой операции дробное число "нормализуется", т.е. возвращается в отрезок

,

путём увеличения или уменьшения экспоненты.

 

При перемножении или делении двух таких чисел надо соответственно сложить либо вычесть их экспоненты. При сложении или вычитании перед выполнением этой операции числа следует привести к одной экспоненте, для чего одно из них домножается на 10 в степени разности экспонент.

Наконец, понятно, что не обязательно выбирать 10 в качестве основания экспоненты. Исходя из устройства встроенных типов с плавающей точкой, самым выгодным представляется класть основание равным 2.

Дискретное логарифмирование

Задача дискретного логарифмирования заключается в том, чтобы по данным целым , , решить уравнение:

где и взаимно просты (примечание: если они не взаимно просты, это конечно не означает, что решений нет или находить их легко; просто в этом случае описанный ниже алгоритм является некорректным).

Здесь описан алгоритм, известный как "baby-step-giant-step algorithm", предложенный Шэнксом (Shanks) в 1971 г., работающий за время за .

Алгоритм

Итак, имеем уравнение:

Воспользуемся для его решения методом Meet-in-the-Middle. Для этого преобразуем уравнение. Положим

где — это заранее выбранная константа, зависящая от . Иногда называют "giant step" (поскольку увеличение его на единицу увеличивает сразу на ), а в противоположность ему — "baby step".

Очевидно, что любое (а мы рассматриваем только

) можно представить в такой форме, причём для

этого будет достаточно значений:

 

 

 

 

 

Тогда уравнение принимает вид:

откуда, пользуясь тем, что и взаимно просты, получаем:

Чтобы решить исходное уравнение, нужно найти соответствующие значения и , чтобы значения левой и правой частей совпали. Иначе говоря, надо решить уравнение:

Эта задача решается с помощью метода Meet-in-the-Middle следующим образом. Посчитаем значения функции

для всех значений аргумента , и отсортируем эти значения. Затем будем перебирать значение второй переменной , вычислять вторую функцию , и искать это значение среди предвычисленных значений первой функции с

помощью бинарного поиска.

Асимптотика

Сначала оценим время вычисления каждой из функций

и

. И та, и другая содержит возведение в

степень, которое можно выполнять с помощью алгоритма бинарного возведения в степень. Тогда функцию мы можем вычислить за время , а — за время .

Сам алгоритм в первой части содержит вычисление функции

для каждого возможного значения и

дальнейшую сортировку значений, что даёт нам асимптотику:

 

 

 

 

 

Во второй части алгоритма происходит вычисление функции

для каждого возможного значения и бинарный

поиск по массиву значений , что даёт нам асимптотику:

 

 

 

 

 

Теперь, когда мы сложим эти две асимптотики, практически очевидно, что минимум достигается, когда

, т.

е. для оптимальной работы алгоритма константу следует выбирать равной:

 

 

 

 

 

При таком выборе асимптотика алгоритма принимает вид:

 

 

 

 

Примечание. Мы могли бы обменять ролями

и

(какую сортировать и затем выполнять по ней бинарный

поиск), однако результат от этого не изменится.

 

 

Реализация

 

 

 

Функция

выполняет бинарное возведение числа в степень по модулю , см. Бинарное возведение

в степень.

 

 

 

Функция производит собственно решение задачи.

int powmod (int a, int b, int m) { int res = 1;

while (b > 0) if (b & 1)

res = (res * a) % m, --b;

else

a = (a * a) % m, b >>= 1; return res;

}

int solve (int a, int b, int m) {

int msq = (int) sqrt (m + .0) + 1;

int msq2 = m / msq + (m % msq ? 1 : 0); vector < pair<int,int> > vals (msq2); for (int i=1; i<=msq2; ++i)

vals[i-1] = make_pair (powmod (a, i * msq, m), i); sort (vals.begin(), vals.end());

for (int i=0; i<=msq; ++i) {

int cur = powmod (a, i, m); cur = (cur * b) % m;

vector < pair<int,int> > ::iterator it =

lower_bound (vals.begin(), vals.end(), make_pair (cur, 0)); if (it != vals.end() && it->first == cur)

return it->second * msq - i;

}

return -1;

}

Линейные диофантовы уравнения с двумя переменными

Диофантово уравнение с двумя неизвестными имеет вид:

где — заданные целые числа, и — неизвестные целые числа.

Ниже рассматриваются несколько классических задач на эти уравнения: нахождение любого решения, получение всех решений, нахождение количества решений и сами решения в определённом отрезке, нахождение решения с наименьшей суммой неизвестных.

Вырожденный случай

Один вырожденный случай мы сразу исключим из рассмотрения: когда . В этом случае, понятно, уравнение имеет либо бесконечно много произвольных решений, либо же не имеет решений вовсе.

Нахождение одного решения

Найти одно из решений диофантова уравнения с двумя неизвестными можно с помощью Расширенного алгоритма Евклида.

Расширенный алгоритм Евклида по заданным и находит их наибольший общий делитель , а также такие коэффициенты и , что:

 

 

 

Утверждается, что если делится на

, то диофантово уравнение

имеет решение;

в противном случае диофантово уравнение решений не имеет. Доказательство следует из очевидного факта, что линейная комбинация двух чисел по-прежнему должна делиться на их общий делитель.

Предположим, что делится на , тогда, очевидно, выполняется:

т.е. одним из решений диофантова уравнения являются числа:

Получение всех решений

Покажем, как получить все остальные решения (а их бесконечное множество) диофантова уравнения, зная одно из решений .

Итак, пусть , а числа удовлетворяют условию:

Тогда заметим, что, прибавив к число и одновременно отняв от , мы не нарушим равенства:

Очевидно, что этот процесс можно повторять сколько угодно, т.е. все числа вида:

являются решениями диофантова уравнения.

Более того, только числа такого вида и являются решениями, т.е. мы описали множество всех решений диофантова уравнения (оно получилось бесконечным, если не наложено дополнительных условий).

Нахождение количества решений и сами решения в заданном отрезке

Пусть даны два отрезка

и

, и требуется найти количество решений

диофантова

уравнения, лежащих в данных отрезках соответственно.

 

 

Заметим, что если одно из чисел

равно нулю, то задача имеет не больше одного решения, поэтому эти случаи мы

в данном разделе исключаем из рассмотрения.

 

 

Сначала найдём решение с наименьшим

таким, что

. Для этого сначала найдём

любое решение диофантова уравнения (см. пункт 1). Затем получим из него решение с наименьшим

— воспользуемся процедурой, описанной в предыдущем пункте, и будем уменьшать/увеличивать , пока оно не попадёт в заданный отрезок, и при этом будет наименьшим. Это можно сделать за таким образом:

int

a, b, c, g, // коэффициенты диофантова уравнения, и g=gcd(a,b) x0, y0, // одно из решений диофантова уравнения

x1, x2, // заданный отрезок

mx, my; // искомое решение с наименьшим x >= x1 int cnt = (x1 - x0) / b;

if (x0 + cnt * b < x1) ++cnt;

mx = x0 + cnt * b; my = y0 - cnt * b;

Здесь предполагается, что (если , то нужно предварительно изменить знаки ). Аналогичным образом найдём решение с наибольшим .

Теперь, если

 

, то количество решений равно нулю.

 

 

 

Если же

 

, то нам осталось наложить условие на :

.

 

 

Пусть

и

— это соответствующие решения для

и

. Если

,

то обменяем их местами. Теперь нам нужно найти пересечение отрезка

и

. Будем

 

увеличивать

(на

), пока оно не станет больше либо равно , и будем уменьшать

(опять же, на

), пока оно не станет меньше либо равно . После этого посчитаем для каждого из полученных

и

значения решений , и если хотя бы одно из них не попало в отрезок

, то задача решений не имеет.

Иначе же, ответом на задачу будет являться величина

.

 

 

Таким образом, мы можем найти количество решений в заданном отрезке за

 

, а также вывести

все решения за время, пропорциональное их количеству.

Нахождение решения в заданном отрезке с наименьшей суммой x+y

Здесь на и на также должны быть наложены какие-либо ограничения, иначе ответом практически всегда будет минус бесконечность.

Идея решения такая же, как и в предыдущем пункте: сначала находим любое решение диофантова уравнения, а

затем, применяя описанную в предыдущем пункте процедуру, придём к наилучшему решению. Действительно, мы имеем право выполнить следующее преобразование (см. предыдущий пункт):

Заметим, что при этом сумма меняется следующим образом:

 

 

 

Т.е. если

, то нужно выбрать как можно меньшее значение , если

, то нужно выбрать как можно

большее значение .

 

Если

, то мы никак не сможем улучшить решение, — все решения будут обладать одной и той же суммой.

Таким образом, мы решили эту задачу за асимптотику

.

Модульное линейное уравнение первого порядка

Постановка задачи

Это уравнение вида:

где — заданные целые числа, — неизвестное целое число.

Требуется найти искомое значение , лежащее в отрезке

(поскольку на всей числовой прямой, ясно,

может существовать бесконечно много решений, которые будут отличаться друг друга на

, где — любое

целое число). Если решение не единственно, то мы рассмотрим, как получить все решения.

 

Решение с помощью нахождения Обратного элемента

Рассмотрим сначала более простой случай — когда и взаимно просты. Тогда можно найти обратный элемент к числу , и, домножив на него обе части уравнения, получить решение (и оно будет единственным):

Теперь рассмотрим случай, когда и не взаимно просты. Тогда, очевидно, решение будет существовать не всегда (например, ).

Пусть , т.е. их наибольший общий делитель (который в данном случае больше единицы).

Тогда, если не делится на , то решения не существует. В самом деле, при любом

левая часть уравнения, т.

е.

, всегда делится на , в то время как правая часть на него не делится, откуда и следует,

что решений нет.

 

 

Если же делится на

, то, разделив обе части уравнения на это (т.е. разделив ,

и на ), мы придём к

новому уравнению:

 

 

 

 

 

 

 

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]