Как найти обратный элемент алгоритм евклида

Есть два пути для решения этой задачи.

Путь первый — использование расширенного алгоритма Евклида.

Алгоритм Евклида ищет НОД двух чисел. Расширенный алгоритм Евклида одновременно с этим представляет НОД как целочисленную линейную комбинацию исходных чисел:

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

ПРОВЕДЕНИЕ АНАЛИЗА СВОЙСТВ, ХАРАКТЕРИЗУЮЩИХ ЗАДАННУЮ ТЕМУ

Полем называется множество с двумя определёнными операциями — сложением и умножением, причем имеют место следующие аксиомы:

  1. Множество образует абелеву группу по сложению;
  2. Поле замкнуто относительно умножения, и множество ненулевых элементов образует абелеву группу по умножению;
  3. Закон дистрибутивности:

3. Закон дистрибутивности

Единичный элемент относительно сложения принято обозначать через 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:

Получаемые в процессе алгоритма Евклида матричные элементы A21(n)и A22(n)удовлетворяют равенствам:

равенства

Доказательство

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

получаем

Утверждение вытекает отсюда непосредственно.

Теорема. (малая теорема Ферма).[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

Как видно в таблице весь последний столбец занят единицами, кроме той строки, для которой не существует обратного элемента. Следовательно, программа работает верно.

ВЫВОДЫ ПО ВСЕЙ КУРСОВОЙ РАБОТЕ

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

СПИСОК ИСПОЛЬЗУЕМОЙ ЛИТЕРАТУРЫ

  1. Судоплатов С. В., Овчинникова Е. В. Элементы дискретной математики: Учебник. — М.: ИНФРА-М; Новосибирск: НГТУ, 2003. — 280 с. — (Серия «Высшее образование»).
  1. Блейхут, Ричард Э. Быстрые алгоритмы цифровой обработки сигналов Р. Блейхут; Пер. с англ. И.И. Грушко
  1. Теория и практика кодов, контролирующих ошибки Р. Блейхут; Пер. с англ. И. И. Грушко, В. М. Блиновского; Под ред. К. Ш. Зигангиров

ПРИЛОЖЕНИЕ

Листинг программы:

Модуль «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/32*m,MaxYm);

textbackground(BLUE);

textcolor(WHITE);

clrscr();

PrintHelp();

window(MaxX/3,2,MaxX2,MaxYm);

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)?

  1. Это выражение довольно легко вбивать (1e9+7).
  2. Простое число.
  3. Достаточно большое.
  4. int не переполняется при сложении.
  5. 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: техника с сайта емакса.

Понравилась статья? Поделить с друзьями:
  • Как найти досуг в москве
  • Как найти площадь фигуры шестиугольника
  • Как можно найти человека в яндексе
  • Ees is turned off как исправить
  • Как найти расход бензина в физике