Есть два пути для решения этой задачи.
Путь первый — использование расширенного алгоритма Евклида.
Алгоритм Евклида ищет НОД двух чисел. Расширенный алгоритм Евклида одновременно с этим представляет НОД как целочисленную линейную комбинацию исходных чисел:
Ka∙a + Kb∙b = (a, b)
Как легко заметить, если A и C не являются взаимно простыми, то решения нет, а если являются — то коэффициент при A и будет искомым обратным элементом (для доказательства можно заменить в формуле выше b на C и взять обе части равенства по модулю C).
Рекурсивный алгоритм довольно прост. На очередном шаге большее из двух чисел (для определенности, a) представляется как c + k∙b
, после чего алгоритм вызывается рекурсивно для (b, c)
:
Ka∙(c + k∙b) + Kb∙b = (a, b)
Ka∙c + (Kb + Ka∙k)∙b = (c + k∙b, b) = (c, b)
Kc1∙c + Kb1∙b = (c, b)
Отсюда имеем Ka = Kc1 и Kb = Kb1 — Kc1∙k
Получаем примерно такой алгоритм:
ФУНКЦИЯ НОД(a, b) -> (d, Ka, Kb):
ЕСЛИ (b == 0) ВЕРНУТЬ (a, 1, 0)
(d, Kb1, Kc1) = НОД(b, a % b);
ВЕРНУТЬ (d, Kc1, Kb1 - ⌊a/b⌋ ∙ Kc1);
Итеративный алгоритм столь же прост в реализации, но сложнее в понимании. Проще всего использовать матрицы. Для начала, следует записать преобразование коэффициентов в матричном виде:
| 0 1 |
(Ka Kb) = (Kb1, Kc1) | |
| 1 -⌊a/b⌋ |
Эти матричные множители можно будет накопить:
|K11 K12| | 0 1 | |K11` K12`|
| | = | | | |
|K21 K22| | 1 -⌊a/b⌋ | |K21` K22`|
Получается следующий алгоритм:
ФУНКЦИЯ НОД(a, b) -> (d, Ka, Kb):
K = (1, 0)(0, 1) // Начинаем с единичной матрицы
ПОКА b > 0
K = (K[1, 0], K[1, 1])(K[0, 0] - ⌊a/b⌋∙K[1, 0], K[0, 1] - ⌊a/b⌋∙K[1, 1])
(a, b) = (b, a % b)
ВЕРНУТЬ (a, (K[0, 0], K[0, 1])
Теперь, когда у нас есть НОД, осталось найти НОД(A, C), проверить что он равен 1 и взять (Ka % C) в качестве искомого обратного числа.
Время работы — порядка log A по основанию φ итераций (это связано с тем, что худший случай для алгоритма Евклида — соседние числа Фибоначчи).
Путь второй — использование формулы Эйлера
Если число C заранее известно, или есть достаточно времени на подготовку, то можно воспользоваться формулой Эйлера:
A ^ φ(C) = 1 (mod C) для взаимно простых A и C
Поскольку для имеющих нетривиальные общие делители A и C задача решения все равно не имеет — ограничение нам не помешает.
В соответствии с формулой, ответом будет A ^ (φ(C) - 1) % C
. Быстро найти его можно при помощи алгоритма быстрого возведения в степень:
ФУНКЦИЯ СТЕПЕНЬ (a, x, c):
b = 1
ПОКА x > 0:
ЕСЛИ x - НЕЧЕТНОЕ, ТО
x = x - 1
b = (b * a) % c
ИНАЧЕ
x = x / 2
a = (a * a) % c
ВЕРНУТЬ b
Корректность этого алгоритма легко доказывается если заметить что a ^ x * b
— его инвариант.
Разумеется, после получения ответа надо будет проверить что он правильный, если он будет неверным — значит, ответа вовсе не существует (A и C имеют общие делители).
Этот алгоритм будет работать быстрее чем алгоритм Евклида, потому что тут основание логарифма больше, а сами итерации — проще. Но для применения этого алгоритма требуется заранее знать φ(C)
Пояснительная записка к курсовой работе
по дискретной математике.
Мультипликативно обратные элементы в поле вычетов.
СОДЕРЖАНИЕ
ПРОВЕДЕНИЕ АНАЛИЗА СВОЙСТВ, ХАРАКТЕРИЗУЮЩИХ ЗАДАННУЮ ТЕМУ.. 3
ВЫВОДЫ… 5
ПОСТРОЕНИЕ ПРОГРАММНОЙ МОДЕЛИ.. 5
Выбор метода. 5
Построение алгоритма программы.. 6
Составление программы на BORLANDC++3.1. 8
ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ.. 8
ВЫВОДЫ ПО ВСЕЙ КУРСОВОЙ РАБОТЕ.. 9
СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ… 10
ПРИЛОЖЕНИЕ.. 11
ПРОВЕДЕНИЕ АНАЛИЗА СВОЙСТВ, ХАРАКТЕРИЗУЮЩИХ ЗАДАННУЮ ТЕМУ
Полем называется множество с двумя определёнными операциями — сложением и умножением, причем имеют место следующие аксиомы:
- Множество образует абелеву группу по сложению;
- Поле замкнуто относительно умножения, и множество ненулевых элементов образует абелеву группу по умножению;
- Закон дистрибутивности:
Единичный элемент относительно сложения принято обозначать через 0 и называть нулём, аддитивный обратный элементу a элемент — через — a; единичный элемент относительно умножения обозначать 1 и называть единицей, мультипликативный обратный к элементу a элемент — через a-1.
Поле с конечным числом элементов называют полями Галуа GF(q).
Вычеты по mod(m) определяют разбиение множества целых чисел на m классов эквивалентности: {C0, C1, C2, …, Cm-1}, где Ci={i, i+m, i+2m, …}. Например, для неотрицательных целых чисел по mod(4) имеется четыре класса —
C0={0, 4, 8, 12, …} C2={2, 6, 10, 14, …}
C1={1, 5, 9, 13, …} C3={3, 7, 11, 15, …}
Для представителей можно ввести операции сложения и умножения по mod(4):
0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | |||
0 | 0 | 1 | 2 | 3 | 0 | 0 | 0 | 0 | 0 | |
1 | 1 | 0 | 3 | 2 | 1 | 0 | 1 | 2 | 3 | |
2 | 2 | 3 | 0 | 1 | 2 | 0 | 2 | 3 | 1 | |
3 | 3 | 2 | 1 | 0 | 3 | 0 | 3 | 1 | 2 |
Для многочлена, как и для чисел, можно ввести сравнение многочлена a(x) по модулю многочлена q(x): a(x)=r(x) mod q(x). Следовательно, можно говорить и о поле многочленов GF(q) над числовым полем GF(p). Если GF(2) и q(x)=x3+1, то имеем поле многочленов GF(x3+1), состоящее из восьми многочленов: {0, 1, x, x+1, x2, x2+1, x2+x, x2+x+1}. Здесь p называется характеристикой поля GF(p); характеристика определяет размерность или порядок полей многочлена: q=pn=23=8, где n — степень многочлена q(x). Перемножая и складывая многочлены по данному модулю можно составить таблицу умножения и сложения. для GF(x2+x+1), размерность которого в два раза меньше описанного выше поля {0, 1, x, x+1} таблицы умножения и сложения представлены выше (если каждому элементу последнего множества сопоставить в соответствие число {0, 1, 2, 3}).
Наибольший общий делитель двух заданных положительных чисел s и t может быть вычислен с помощью итеративного применения алгоритма деления. Эта процедура известна как алгоритм Евклида. Предположим, что t<s; алгоритм Евклида сводится к последовательности шагов:
s=Q(1)t+t(1)
t=Q(2)t(1)+t(2)
t(1)=Q(3) t(2)+t(3)
……….
t(n-2)=Q(n)t(n-1)+t(n)
t(n-1)=Q(n+1)t(n)
где остановка процесса наступает при получении нулевого остатка. Последний ненулевой остаток t(n) равен наибольшему общему делителю. Этот факт будет доказан в следующей теореме. Матричные обозначения позволяют кратко записать шаги алгоритма Евклида в виде:
Теорема. (Алгоритм Евклида).[2][3] Для двух заданных положительных чисел s и t, где s>t, пусть s(0)=s и t(0)=t. Решение рекуррентных уравнений:
при r=1, …, n даётся величиной
где n равно наименьшему целому числу, для которого t(n)=0.
Доказательство:
Так как t(r+1)<t(r) и все остатки неотрицательны, то в конце концов наступит n, для которого t(n)=0, так что завершение работы алгоритма произойдёт обязательно, легко проверить, что
Поэтому
так что s(n) должно делить оба числа s и t и, следовательно, делит НОД[s, t]. Далее
так что любой делитель чисел s и t делит s(n). Следовательно, НОД[s, t] делит s(n) и делится на s(n). Таким образом
s(n)=НОД[s, t]
Это завершает доказательство.
Из этой теоремы вытекает несколько важных следствий. Пусть
Тогда получаем следствие:
Для любых чисел s и t найдутся такие целые числа a и b, что
НОД[s, t]=as+bt
Доказательство
Достаточно доказать следствие для положительных s и t. Так как
и s(n)=НОД[s, t], то утверждение выполняется при и .
Следствие 2:
Получаемые в процессе алгоритма Евклида матричные элементы и удовлетворяют равенствам:
Доказательство
Используя выписанные выше выражения для обратной матрицы и обращая первое равенство из доказательства прошлого следствия получаем
Утверждение вытекает отсюда непосредственно.
Теорема. (малая теорема Ферма).[1] Если m – простое число и a – произвольное целое число, не делящееся на m, то am-1=1 (mod m).
Доказательство.
a*am-2=am-1=1 (mod m).
Следствие. Если m – простое число, то в кольце Zm выполняется равенство a-1=am-2.
Пример: если a=2, m=5, то 2-1=25-2(mod 5)=8(mod 5)=3(mod 5). Тогда 2-1=3.
Нахождение обратных элементов с использованием алгоритма Евклида[1]
Так как у поля модуль m – простое число, то для нахождения обратного элемента для элемента x можно найти уравнение ax+my=1. Тогда слагаемое my равно нулю (вычисления производятся по модулю m). Следовательно, элемент a является обратным к элементу x.
Нахождения обратных чисел можно реализовать с помощью малой теоремы Ферма.
В соответствии с малой теоремой Ферма a-1=am-2.
ВЫВОДЫ
В соответствии с вышеописанными теоремами программа должна находить обратный элемент используя малую теорему Ферма или алгоритм Евклида. Программа должна работать для достаточно широкого спектра значений модуля, по которому производятся вычисления и элемента для которого вычисляется обратный элемент. То есть программа не должна вычислять обратный элемент, например, только для чисел, которые меньше 3 или 5 (для этих чисел обратный элемент в поле вычетов по такому маленькому модулю можно вычислить и без компьютера). То есть программа должна быть предназначена для вычисления обратного элемента для достаточно большого (в разумных пределах) числа. Также программа должна проверять корректность входных данных. То есть, в случае, если НОД введённого модуля и элемента должно равняться единице, в противном случае программа должна сообщать, о том, что данный элемент не имеет обратного.
ПОСТРОЕНИЕ ПРОГРАММНОЙ МОДЕЛИ
Напишем программу нахождения обратного числа для данного элемента в данном поле.
Поле возьмём по модулю простого числа.
Выбор метода
По началу кажется, что нахождение обратных элементов в поле проще всего реализовать с помощью малой теоремы Ферма. Но на практике из-за ограниченности разрядной сетки машины этот алгоритм использовать достаточно затруднительно. Например, для вычисления обратного элемента для 9 в поле по модулю 23:
921=109418989131512359209.
Можно приводить числа по данному модулю после каждого умножения, что решит эту проблему для не очень больших чисел. Но для достаточно больших чисел эта проблема останется.
Причём значение имеет даже последняя девятка. хранения такого большого числа в компьютере и выполнения с ним операций вызывает определённые трудности. Следовательно, лучше реализовать алгоритм Евклида, работающий со стандартными типами данных, хотя для некоторых случаев (при небольших числах) алгоритм, построенный на следствии малой теоремы Ферма будет подходить больше.
Построение алгоритма программы
Напишем программу, реализующую нахождение обратного элемента с использованием алгоритма Евклида. По алгоритму Евклида (учитывая, что НОД[a, m]=1) выполняются следующие соотношения:
s=Q(1)t+t(1)
t=Q(2)t(1)+t(2)
t(1)=Q(3) t(2)+t(3)
……….
t(n-3)=Q(n-1)t(n-2)+t(n-1)
t(n-2)=Q(n)t(n-1)+t(n)
t(n-1)=Q(n+1)t(n)+1
Для нахождения уравнения ax+my=1 выразим с помощью предпоследнего уравнения число 1 через t(n-1)и t(n-2):
1= t(n-1) -Q(n+1)t(n)
1= t(n-1)— Q(n+1)* (t(n-2)- Q(n)t(n-1))
1= (1+Q(n+1) *Q(n))*t(n-1)— Q(n+1) *t(n-2)
1= — Q(n+1) *t(n-2)+ (1+Q(n+1) *Q(n))*t(n-1)
Как отсюда видно множитель — Q(n+1) «перешёл» из правого слагаемого (из слагаемого с большим) в левое слагаемое (с меньшим индексом). Проверим дальше. Теперь выразим 1 через t(n-2) и t(n-3):
1= — Q(n+1) *t(n-2)+ (1+Q(n+1) *Q(n))*t(n-1)
1= — Q(n+1) *t(n-2)+ (1+Q(n+1) *Q(n))*( t(n-3)— Q(n-1)t(n-2))
1= (1+Q(n+1) *Q(n))*t(n-3)+ ((1+Q(n+1) *Q(n))*( — Q(n-1)) — Q(n+1))t(n-2) (1)
Как видно снова множитель из правого слагаемого перешёл в левое слагаемое. Правое слагаемое составилось из прошлого множителя правого слагаемого, умноженного на следующее частное, и прошлого левого слагаемого. То есть, если на данном шаге перед слагаемым t с меньшим индексом стоит множитель a, а перед другим слагаемым – b, то для следующего шага перед слагаемым с меньшим индексом будет стоять b, а перед другим слагаемым b*a-a.
На основании полученной закономерности составим алгоритм нахождения НОД по алгоритму Евклида. Для хранения Q(i)проще всего использовать стек, так как в данном случае на каждом шаге будет необходим Q(i), который был сохранён последним.
Графическая схема алгоритма, осуществляющего нахождение НОД с помощью алгоритма Евклида:
Этот алгоритм при использовании для нахождения обратных элементов в качестве числа t должен получать модуль в котором производить вычисления, а в качестве числа s элемент, для которого необходимо найти обратный элемент. Тогда в выходной переменной s2 будет получаться обратный элемент для числа s. Естественно при получении входных данных модуль t должен быть таким, что полученная структура была бы полем. То есть число t должно быть простым. (когда t – простое, то НОД[t, s]=1 и в полученном выражении слагаемое с t сокращается).
Составление программы на BORLANDC++3.1
Для хранения модуля и введённого числа подходит тип long (для того, чтобы программа могла работать с более длинными числами).
Для реализации стека был составлен специальный модуль, в котором были реализованы стандартные процедуры работы со стеком (PUSH (добавление элемента в стек), POP (извлечение элемента из стека), EMPTY (проверка стека на пустоту)).
С использованием стека была разработана процедура, вычисляющая с помощью алгоритма Евклида НОД двух чисел и возвращающая также полученное уравнение.
Для реализации интерфейса, понятного пользователю в данном случае не обязательно использовать графический режим, так как программа не работает с графическими объектами. Для работы с текстовым экраном был выбран стандартный модуль <CONIO.H>, поскольку в нём содержатся все необходимые процедуры: для вывода текстовой информации на экран, изменения цвета, для создания текстового окна и считывания информации, вводимой пользователем
ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ
Для проверки работоспособности программы достаточно вычисленный обратный элемент умножить на данный элемент и взять остаток от деления полученного числа на модуль, по которому производились вычисления. Если получится единица, то обратный элемент вычислен верно. Для сравнения были произведены вычисления с использованием малой теоремы Ферма и калькулятора.
a
(элемент, для которого находится обратный эдемент) |
m
(модуль, по которому производятся вычисления) |
am-2 | a(m-2) (mod(m))
(найденный вручную обратный элемент) |
Значение обратного элемента, вычисленное с помощью разработанной программы | a*a-1 (mod m) |
9 | 23 | 109418989131512359209 | 18 | 18 | 1 |
2 | 5 | 8 | 3 | 3 | 1 |
56 | 101 | 1,176561609770433870981098575571e+173 | 92 | 92 | 1 |
999 | 1001 | 3,6806348825922326789470084006052e+2996 | 720 | 500 | для вычисленного вручную:
562 для вычисленного с помощью программы: 1 |
1111 | 30001 | не удалось вычислить | не удалось вычислить | 22494 | 1 |
77777 | 100001 | не удалось вычислить | не удалось вычислить | 35714 | 1 |
4555 | 7653765 | не удалось вычислить | не удалось вычислить | не существует обратного элемента | — |
344533 | 10000001 | не удалось вычислить | не удалось вычислить | 1644429 | 1 |
44256323 | 843553333 | не удалось вычислить | не удалось вычислить | 759177439 | 1 |
Как видно в таблице весь последний столбец занят единицами, кроме той строки, для которой не существует обратного элемента. Следовательно, программа работает верно.
ВЫВОДЫ ПО ВСЕЙ КУРСОВОЙ РАБОТЕ
Анализируя составленную программу можно сделать вывод, что с помощью алгоритма Евклида можно достаточно быстро найти обратный к данному элемент по данному модулю. При тестировании программы работала с числами около одного или двух миллиардов практически мгновенно. Никакого замедления не было замечено.
СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ
- Судоплатов С. В., Овчинникова Е. В. Элементы дискретной математики: Учебник. — М.: ИНФРА-М; Новосибирск: НГТУ, 2003. — 280 с. — (Серия «Высшее образование»).
- Блейхут, Ричард Э. Быстрые алгоритмы цифровой обработки сигналов Р. Блейхут; Пер. с англ. И.И. Грушко
- Теория и практика кодов, контролирующих ошибки Р. Блейхут; Пер. с англ. И. И. Грушко, В. М. Блиновского; Под ред. К. Ш. Зигангиров
ПРИЛОЖЕНИЕ
Листинг программы:
Модуль «STEK.CPP»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
//В этом модуле описаны процедуры работы со стеками //константы для реализации процедур const true=1; const false=0; //Эта структура описывает 1 ячейку стека struct TStek{ long info; TStek * last; }; //создаём указатель NULL TStek Zero; TStek * NULLS=&Zero; //////////////////////////////////////////////////////////////////////////// //Функция Push служит для занесения значения переменной в стек //Если нет места лдя занесения переменной в стек, то функция возвращает //0(false), иначе 1(true) int Push(TStek *f,long x) { TStek *lf; if (!(lf=new TStek)) return false; (*lf).last=(*f).last; (*lf).info=(*f).info; (*f).info=x; (*f).last=lf; return true; } //////////////////////////////////////////////////////////////////////////// //Функция служит для извлечения переменной из стека //Функция возвращает значение переменной, находившейся в вершине стека long Pop(TStek * f) { TStek *lf; long x=(*f).info; lf=(*f).last; (*f).info=(*lf).info; (*f).last=(*lf).last; delete(lf); return x; } //////////////////////////////////////////////////////////////////////////// //Функция служит для проверки стека на пустоту //Возвращаемые значения: //Если стек пуст, то функция возвращает 1(true), иначе 0(false) int Empty(TStek *f) { if ((*f).last==NULLS) return true; else return false; } //////////////////////////////////////////////////////////////////////////// //Функция инициалтзирует Стек TStek * InitStek(TStek* f) { f=new TStek; (*f).last=NULLS; return f; } void DeleteStek(TStek *f) { delete(f); } |
Распечатка главной программы «REVKLID3.CPP»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
#include <conio.h> #include <ALLOC.H> #include «STEK.CPP» //#include «MOUSE.CPP» #define version 2.1 #define _RELEASE ///////////////////////////////////////////////////////////////////////////// //Прототипы всех функций long RAlgEvklid(long t,long s,long * s1,long * s2); void PrintHelp(); void VerifyT(long t); void all(long s, long t); ///////////////////////////////////////////////////////////////////////////// char cmod[5]=«mod «; char cexit[5]=«exit»; char call[5]=«all «; int cm=0,cm2=0; void main(void) { directvideo=1; _setcursortype(_SOLIDCURSOR); textbackground(BLACK); clrscr(); textmode(C80); unsigned char MaxX=80,MaxY=25; int n=25,m=1; window(2,2,MaxX/3—2*m,MaxY—m); textbackground(BLUE); textcolor(WHITE); clrscr(); PrintHelp(); window(MaxX/3,2,MaxX—2,MaxY—m); clrscr(); long s,t; cprintf(«rnПрограмма вычисляет обратный элемент в поле вычетов. «); cprintf(«rnПо какому модулю производить вычисления: rn»); cscanf(«%li»,&t); VerifyT(t); while (cm!=—4) { cm=0; textcolor(WHITE); cprintf(«rnrnВведите число, для которого хотите вычислить обратный элемент:rn»); char command[128]=«»; s=—1; textcolor(GREEN); cscanf(«%li»,&s); if (s==—1) { cscanf(«%s»,&command); textcolor(YELLOW); for (n=0; n<=3;n++) { if (command[n]==cmod[n]) cm++; if (command[n]==cexit[n]) cm—; if (command[n]==call[n]) cm2++; } #ifdef _DEBUG cprintf(«rncm=%li, cm2=%lirn»,cm,cm2); #endif if (cm==3) { cscanf(«%li»,&t); VerifyT(t); } if (cm2==3) { all(s,t); cm2=0; } continue; } textcolor(WHITE); if (s>=t)//Приведение данного числа под данный модуль { cprintf(«rn%li(mod %li)=»,s,t); s=s % t; cprintf(«%li»,s); } if (s>0) { long s1,s2; long NOD; NOD=RAlgEvklid(s,t,&s1,&s2); cprintf(«rnНОД[%li,%li]=%li*%li+%li*%li=%li»,s,t,s1,s,t,s2,NOD); if (s1<0) s1=s1+t; #ifdef _RELEASE double vr=NOD; vr=(s*s1)%t; cprintf(«rnПроверка результата: %li*%li=%li (mod %li)»,s,s1,long(vr),t); #endif if (NOD==1) cprintf(«rnОбратный элемент к числу %li это %li.»,s,s1); else { textcolor(RED); cprintf(«rnОшибка! НОД не равно единице.»); textcolor(WHITE); } } else { textcolor(RED); cprintf(«rnОшибка! Введите число больше 0.»,t); textcolor(WHITE); } } } ///////////////////////////////////////////////////////////////////////////// //Функция вычисляет НОД с при помощи расширенного алгоритма Евклида long RAlgEvklid(long t,long s,long * s1,long * s2) { TStek * Mods=InitStek(Mods);//Объявление и инициализация стека для хранения остатков (вычетов) TStek * Q=InitStek(Q);//Объявление и инициализация стека для хранения результатов int Change=false; //int FillValue=sizeof(TStek); if (t>s)//Число s должно быть больше числа t { long buffer=t; t=s; s=buffer; Change=true; } long result=t; long t2=s; long t1=result; #ifdef _DEBUG long bbbb; #endif while (t1!=0) //Реализация алгоритма Евклида { result=t1; t1=t2 % result; #ifdef _DEBUG // bbbb=t2/result; // Push(Mods,t1);//Запись остатка от деления в стек, для последующего вычисления уравнения // cprintf(«t1=%i, t2=%i, result=%i, t2/result=%i»,t1,t2,result,bbbb); #endif if (!Push(Q,long(t2/result)))//Запись результата деления в стек, для последующего вычисления уравнения cprintf(«rnrnНехватка памяти.rnrn»); #ifdef _DEBUG cprintf(«s/t=%li, s=%li, t=%li, t2/result=%lirn»,s/t,s,t,long(t2/result)); #endif t2=result; } int count=0; long b=1;// if (Empty(Q)) cprintf(«rnСтек пустrn»); else cprintf(«Стек не пуст»); // if (!Empty(Q)) {b=Pop(Q); count++;cprintf(«rn%i»,b);}; if (!Empty(Q)) {b=-Pop(Q); count++;cprintf(«rn%i»,b);} long buffer;long bbb=1;long a=1; // cprintf(«b=%li, bbb=%li, a=%lirn»,b,bbb,a); while (!Empty(Q))//Вычисление уравнения (чисел s1 и s2) { buffer=b; bbb=Pop(Q); count++; b=a—(b*bbb); a=buffer; #ifdef _DEBUG cprintf(«b=%li, bbb=%li, a=%lirn»,b,bbb,a); #endif } #ifdef _DEBUG cprintf(«rnКоличество элементов в стеке=%irn», count); #endif if (Change) { buffer=b; b=a; a=buffer; } //cprintf(«a=%li, b=%lirn»,a,b); (*s1)=b; (*s2=a); // if () // { // buffer=a; // a=b; b=buffer; // } DeleteStek(Mods); DeleteStek(Q); return result; } ///////////////////////////////////////////////////////////////////////////// //Функция выводит помощь на экран void PrintHelp() { //cprintf(«************************»); cprintf(«Программа вычиляетrn»); cprintf(«число, обратное к дан-rn»); cprintf(«ному по данному модулю.rn»); cprintf(«При запуске программа rn»); cprintf(«запрашивает модуль, поrn»); cprintf(«которому будут произ-rn»); cprintf(«водиться вычисленияrnrn»); cprintf(«Вы в любой момент мо-rn»); cprintf(«жете изменить модуль rn»); cprintf(«введя команду:rn»); textcolor(RED); cprintf(«mod <новый модуль>rnrn»); textcolor(WHITE); cprintf(«Для вычисление обрат-rn»); cprintf(«ных элементов дляrn»); cprintf(«всего поля введите:rn»); textcolor(RED); cprintf(«allrnrn»); textcolor(WHITE); cprintf(«Для выхода из программы»); cprintf(«введите:rn»); textcolor(RED); cprintf(«exit»); textcolor(WHITE); } ///////////////////////////////////////////////////////////////////////////// //Функция проверяет модуль void VerifyT(long t) { cprintf(«Новый модуль: %li»,t); if (t<0) { textcolor(LIGHTRED); cprintf(«rnОшибка! Недопустимое значение переменной.»); cprintf(«rnПеременная не должна принимать отрицательные значения.»); textcolor(WHITE); } else { char ch1; cprintf(«rnПроверить, является ли данное число простым? (y/n)rn»); cscanf(«%c»,ch1); ch1=getch(); if (ch1==‘y’) { short int pole=1; long n; n=2; if ((t%2==0)&&(t!=2)) pole=0; else { for (n=3; n<t;n=n+2) { cprintf(«%lir»,n); if (t%n==0) { pole=0; break; } } } if (!pole) { textcolor(RED); cprintf(«Число %li не является простым.rnОно делится, например, на %li. Измените модуль.rn»,t,n); } else cprintf(«Число %li является простым.rn»,t); } } textcolor(WHITE); cprintf(«Проверка завершена.rn»); } //////////////////////////////////////////////////////////////////////////////// //Функция вычисляет обратные элементы для всех элементов поля void all(long s, long t) { char ch; char KeyEsc=27; long s1,s2, NOD; double vr; #define Zagolovok cprintf(«rnЭлемент |Обратный |Обр.*Элем.(mod %li)»,t); Zagolovok cscanf(«%c»,&ch); for (s=1; s<t; s++) { NOD=RAlgEvklid(s,t,&s1,&s2); if (s1<0) s1=s1+t; vr=(s*s1)%t; if (NOD==1) cprintf(«rn%11li|%11li|%11li»,s,s1,long(vr)); else cprintf(«rn%11li| -| -«,s); if (s % 21==0) { cprintf(«rnНажмите любую клавишу…»); cscanf(«%c»,&ch); if (ch==KeyEsc) break; Zagolovok } } cprintf(«rnВывод завершён. Нажмите любую клавишу…»); cscanf(«%c»,&ch); } |
Обратный по модулю
❓Инструкция
Калькулятор онлайн для вычисления обратного элемента по модулю в кольце. Алгоритм поддерживает работу с большими числами с некоторыми ограничениями.
Использование:
Заполняются два поля — число a и модуль m. Число a — число к которому ищем обратный, m — модуль, по которому ищем.
Калькулятор выдает обратный элемент после нажатия на кнопку «Вычислить».
Если установлена галочка «подробнее», то калькулятор помимо обратного элемента по модулю выдает некоторые этапы вычисления.
Ограничения:
Калькулятор поддерживает работу с большими целыми числами (в том числе отрицательными числами для числа a, и только положительными для модулю m) длиной не более 10 000 символов.
📖 Теория
Что значит по модулю?
Синонимом к данному выражению является выражение «остаток от деления«. То есть выражение «5 по модулю 3» эквивалентно выражению «остаток от деления 5 на 3». И в том и в другом случае подразумевается в ответе число 2, так как остаток от деления 5 на 3 = 2.
Стоить отметить тот факт, что по модулю m мы имеем числа от 0 до m — 1. Действительно, остаток от деления на m никогда не превысит m — 1.
Что такое обратное?
Напомним, что число, умноженное на его обратное, равно 1. Из базовой арифметики мы знаем, что:
Число, обратное к числу A, равно 1 / A, поскольку A * (1 / A) = 1 (например, значение, обратное к 5, равно 1/5).
Все действительные числа, кроме 0, имеют обратную
Умножение числа на обратное к A эквивалентно делению на A (например, 10/5 соответствует 10 * 1/5)
Что такое обратное по модулю?
В модульной арифметике у нас нет операции деления. Тем не менее, у нас есть модульные инверсии.
Модульная инверсия a (mod m) есть a-1
(a * a-1) ≡ 1 (mod m) или эквивалентно (a * a-1) mod m = 1
Только числа, взаимно простые с модулем m, имеют модульное обратное.
Говоря проще, обратным элементом к a по модулю m является такое число b, что остаток от деления (a * b) на модуль m равно единице (a * a-1) mod m = 1
Как найти модульный обратный
Наивный метод нахождения модульного обратного a ( по модулю m) является:
Шаг 1. Рассчитать a * b mod m для значений b от 0 до m — 1
Шаг 2. Модульная инверсия a mod m — это значение b, при котором a * b mod m = 1
Обратите внимание, что термин b mod m может иметь только целочисленное значение от 0 до m — 1, поэтому тестирование больших значений чем (m-1) для b является излишним.
Вы наверно уже догадались, что наивный метод является очень медленным. Перебор всех чисел от 0 до m-1 для большого модуля довольно-таки трудоемкая задача. Существует гораздо более быстрый метод нахождения инверсии a (mod m). Таковым является расширенный алгоритм Евклида, который реализован в данном калькуляторе.
Расширенный алгоритм Евклида
Представим наибольший общий делитель числа a и модуля m в виде ax + my. То есть НОД(a, m) = ax + my. Помним, что обратный элемент существует только тогда, когда a и m взаимно просты, то есть их НОД(a, m) = 1. Отсюда: ax + my = 1 — линейное диофантово уравнение второго порядка. Необходимо решить данное уравнение в целых числах и найти x, y.
Найденный коэффициент x будет являться обратным элементом к a по модулю m. Это следует оттуда, что, если мы возьмём от обеих частей уравнения остаток по модулю m, то получим: ax = 1 (m).
Расширенный алгоритм Евклида, в отличие от классического, помимо наибольшего общего делителя позволяет найти также коэффициенты x, y.
Алгоритм:
Вход: a, m ≠ 0
Выход: d, x, y, такие что d = gcd(a, m) = ax + my
1. [Инициализация] (a0, a1) := (a, m); (x0, x1) := (1, 0); (y0; y1) := (0, 1).
2. [Основной цикл] Пока a1 ≠ 0 выполнять {q = QUO(a0, a1);
(a0, a1) := (a1, a0 — a1q); (x0, x1) := (x1, x0 — x1q); (y0, y1) := (y1, y0 — y1q);
QUO(a0, a1) — целая часть от деления a0 на a1
3. [Выход] Вернуть (d, x, y) = (a0, x0, y0)
Битовая сложность расширенного алгоритма Евклида равна O((log2(n))2) , где n = max (|a|, |m|)
Непонятен алгоритм? Ничего страшного, примеры ниже именно для этого и предназначены.
➕ Примеры
Пример для наивного метода.
Пусть a = 3, m = 7. То есть нам необходимо найти обратный элемент к 3 по модулю 7.
Шаг 1. Рассчитать a * b mod m для значений B от 0 до m-1. По очереди проверяем числа от 0 до 6.
3 * 0 ≡ 0 (mod 7) — не подходит
3 * 1 ≡ 3 (mod 7)
3 * 2 ≡ 6 (mod 7)
3 * 3 ≡ 9 ≡ 2 (mod 7)
3 * 4 ≡ 12 ≡ 5 (mod 7)
3 * 5 ≡ 15 (mod 7) ≡ 1 (mod 7) <—— Обратное найдено.
3 * 6 ≡ 18 (mod 7) ≡ 4 (mod 7)
при b = 5 выполнилось условие, что a * b ≡ 1 (m). Следовательно, b = 5 является обратным элементом к 3 по модулю 7.
Пример на расширенный алгоритм Евклида.
Пусть аналогично предыдущему примеру имеем a = 3, m = 7. Также, требуется найти обратный элемент к 3 по модулю 7. Согласно алгоритму начинаем заполнять таблицу на каждом этапе цикла.
Итерация | q | a0 | a1 | x0 | x1 | y0 | y1 |
0 | — | 3 | 7 | 1 | 0 | 0 | 1 |
1 | 0 | 7 | 3 | 0 | 1 | 1 | 0 |
2 | 2 | 3 | 1 | 1 | -2 | 0 | 1 |
3 | 3 | 1 | 0 | -2 | 0 | 1 | -3 |
После 3-ей итерации получили a1 = 0, строго по алгоритму из раздела «Теория» заканчиваем работу алгоритма.
(d, x, y) = (a0, x0, y0)
(d, x, y) = (1, -2, 1), видим, что d = НОД(3, 7) = 1, следовательно числа 3 и 7 являются взаимно простыми, а значит обратный существует.
Делаем проверку:
3 * (-2) + 7 * 1 = 1
-6 + 7 = 1
1 = 1 — верно!
Обратным элементом к тройке по модулю 7 является x = -2. По модулю 7 число -2 равно 5. Получили, что x = 5 является обратным элементом к 3 по модулю 7.
Сайт переезжает. Большинство статей уже перенесено на новую версию.
Скоро добавим автоматические переходы, но пока обновленную версию этой статьи можно найти там.
Обратный элемент по модулю
Часто в задачах требуется посчитать что-то по простому модулю (чаще всего (10^9 + 7)). Это делают для того, чтобы участникам не приходилось использовать длинную арифметику, и они могли сосредоточиться на самой задаче.
Обычные арифметические операции выполняются не сильно сложнее — просто нужно брать модули и заботиться о переполнении. Например:
c = (a + b) % mod;
c = (mod + a - b) % mod;
c = a * b % mod;
Но вот с делением возникают проблемы — мы не можем просто взять и поделить. Пример: (frac{8}{2} = 4), но (frac{8 % 5 = 3}{2 % 5 = 2} neq 4).
Нужно найти некоторый элемент, который будет себя вести как (frac{1}{a} = a^{-1}), и вместо «деления» домножать на него. Назовем такой элемент обратным.
Способ 1: бинарное возведение в степень
Если модуль (p) простой, то решением будет (a^{-1} equiv a^{p-2}). Это следует из малой теоремы Ферма:
Теорема. (a^p equiv a pmod p) для всех (a), не делящихся на (p).
Доказательство. (для понимания несущественно, можно пропустить)
[
begin{aligned}
a^p &= (underbrace{1+1+ldots+1+1}_text{$a$ раз})^p
\ &= sum_{x_1+x_2+ldots+x_a = p} P(x_1, x_2, ldots, x_a) & text{(раскладываем по определению)}
\ &= sum_{x_1+x_2+ldots+x_a = p} frac{p!}{x_1! x_2! ldots x_a!} & text{(какие слагаемые не делятся на $p$?)}
\ &equiv P(p, 0, ldots, 0) + ldots + P(0, 0, ldots, p) & text{(все остальные не убьют $p$ в знаменателе)}
\ &= a
end{aligned}
]
Здесь (P(x_1, x_2, ldots, x_n) = frac{k}{prod (x_i!)}) это мультиномиальный коеффициент — количество раз, которое элемент (a_1^{x_1} a_2^{x_2} ldots a_n^{x_n}) появится при раскрытии скобки ((a_1 + a_2 + ldots + a_n)^k).
Теперь два раза «поделим» наш результат на (a).
[ a^p equiv a implies a^{p-1} equiv 1 implies a^{p-2} equiv a^{-1} ]
Получается, что (a^{p-2}) ведет себя как (a^{-1}), что нам по сути и нужно. Посчитать (a^{p-2}) можно за (O(log p)) бинарным возведением в степень.
Приведем код, который позволяет считает (C_n^k).
int t[maxn]; // факториалы, можно предподситать простым циклом
// бинарное возведение в степень
int bp (int a, int n) {
int res = 1;
while (n) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
// находит обратный элемент как a^(p-2)
int inv (int x) {
return bp(x, mod-2);
}
int c (int n, int k) {
return t[n] * inv(t[k]) % mod * inv(t[n-k]) % mod;
}
Способ 2: диофантово уравнение
Диофантовыми уравнениями называют такие штуки:
[ ax + by = 1 ]
Требуется решить их в целых числах, то есть (a) и (b) известны, и нужно найти такие целые (возможно, отрицательные) (x) и (y), чтобы равенство выполнялось. Решают такие вещи расширенным алгоритмом Евклида. TODO: описать, как он работает.
Подставим в качестве (a) и (b) соответственно (a) и (m)
[ ax + my = 1 ]
Одним из решений уравнения и будет (a^{-1}), потому что если взять уравнение по модулю (m), то получим
[ ax + by = 1 iff ax equiv 1 iff x equiv a^{-1} pmod m ]
Преимущества этого метода над возведением в степень:
- Если обратное существует, то оно найдется даже если модуль не простой. Способ с бинарным возведением тоже можно заставить работать с произвольным модулем, но это будет намного труднее.
- Алгоритм проще выполнять руками.
Сам автор почти всегда использует возведение в степень.
Почему (10^9+7)?
- Это выражение довольно легко вбивать (
1e9+7
). - Простое число.
- Достаточно большое.
int
не переполняется при сложении.long long
не переполняется при умножении.
Кстати, (10^9 + 9) обладает теми же свойствами. Иногда используют и его.
Предподсчёт обратных факториалов за линейное время
Пусть нам нужно зачем-то посчитать все те же (C_n^k), но для больших (n) и (k), поэтому асимптотика (O(n log m)) нас не устроит. Оказывается, мы можем сразу предподсчитать все обратные ко всем факториалам.
Если у нас уже написан inv
, то нам не жалко потратить (O(log m)) операций, посчитав (m!^{-1}).
После этого мы будем считать ((m-1)!^{-1}) как (m!^{-1} m = frac{1}{1 cdot 2 cdot ldots cdot (m-1)}).
int f[maxn];
f[0] = 1;
for (int i = 1; i < maxn; i++)
f[i] = i*f[i-1] % mod;
int r[maxn];
r[maxn-1] = inv(f[maxn-1])
for (int i = maxn-1; i >= 1; i--)
r[i-1] = r[i]*i % mod;
TODO: техника с сайта емакса.