背景
在 Python 扩展欧几里得算法 一文中,我们已经接触了扩展欧几里得算法。借助扩展欧几里得算法,我们可以高效地计算模 n 乘法的逆元。本文会介绍相关的内容。
正文
对正整数 a 和 n 而言,是否可以找到一个整数 x 使得 ax≡1(modn) 成立呢?
由于以下两个等式成立(其中 k 是任意整数),我们只需要关心满足 0≤a,x<n 条件的整数 a,x 就够了。
- ax≡(a−kn)x(modn)
- ax≡a(x−kn)(modn)
我们可以先用比较小的 n 试一试。
用较小的 n 做尝试
n=1 时
| a | 满足 ax≡1(mod1) 和 0≤x<1 的 x |
|---|---|
| 0 | 0 |
n=2 时
| a | 满足 ax≡1(mod2) 和 0≤x<2 的 x |
|---|---|
| 0 | (不存在) |
| 1 | 1 |
n=3 时
| a | 满足 ax≡1(mod3) 和 0≤x<3 的 x |
|---|---|
| 0 | (不存在) |
| 1 | 1 |
| 2 | 2 |
n=4 时
| a | 满足 ax≡1(mod4) 和 0≤x<4 的 x |
|---|---|
| 0 | (不存在) |
| 1 | 1 |
| 2 | (不存在) |
| 3 | 3 |
n=5 时
| a | 满足 ax≡1(mod5) 和 0≤x<5 的 x |
|---|---|
| 0 | (不存在) |
| 1 | 1 |
| 2 | 3 |
| 3 | 2 |
| 4 | 4 |
n=6 时
| a | 满足 ax≡1(mod6) 和 0≤x<6 的 x |
|---|---|
| 0 | (不存在) |
| 1 | 1 |
| 2 | (不存在) |
| 3 | (不存在) |
| 4 | (不存在) |
| 5 | 5 |
n=7 时
| a | 满足 ax≡1(mod7) 和 0≤x<7 的 x |
|---|---|
| 0 | (不存在) |
| 1 | 1 |
| 2 | 4 |
| 3 | 5 |
| 4 | 2 |
| 5 | 3 |
| 6 | 6 |
一些猜测
从上一小节的数据来看,似乎有以下规律成立(有待证明或者证伪)
- 当 a 和 n 互质(即 gcd(a,n)=1)时,似乎总能找到满足 ax≡1(modn) 的 x
- 当我们限制 x 满足 0≤x<n 时,能让 ax≡1(modn) 成立的 x 似乎最多只有 1 个。
第一个规律,我们可以用扩展欧几里得算法来证明。当 a 和 n 互质(即 gcd(a,n)=1)时,借助扩展欧几里得算法,可以找到满足 ax0+ny0=1 的整数 x0,y0。于是 ax0−1=ny0,所以 ax0≡1(modn)。令 x=x0modn,就找到了符合要求的 x。
第二个规律,我们试试反证法。假设可以找到 x1,x2 分别满足
- ax1≡1(modn)
- ax2≡1(modn)
我们在第一个等式两边乘以 x2,会得到
x2ax1≡x2(modn)
我们在第二个等式两边乘以 x1,会得到
x1ax2≡x1(modn)
而模 n 的乘法满足交换律和结合律(这里就不证明了),所以
x2ax1≡ax2x1(modn)
ax2x1≡ax1x2(modn)
ax1x2≡x1ax2(modn)
于是 x1≡x2(modn)。
因此,当我们限制 x 满足 0≤x<n 时,能让 ax≡1(modn) 成立的 x 最多只有 1 个
用程序来处理
核心逻辑
Python 扩展欧几里得算法 一文中已经提供了扩展欧几里得算法的代码。我们在它的基础上,再加上计算逆元的方法即可 ⬇️
python
def extended_euclidean(a, b):
if (a, b) == (0, 0):
raise ValueError("a and b cannot be both 0")
if b == 0:
return (1, 0, a)
x, y, g = extended_euclidean(b, a % b)
return (y, x - a // b * y, g)
def calc_inverse_element(a, n):
x, _, gcd = extended_euclidean(a, n)
if gcd == 1:
return x % n
return None
添加 GUI 之后的完整程序
豆包 帮我完成了和 Pygame 相关的代码。完整的 Python 程序如下 ⬇️
python
import pygame
def extended_euclidean(a, b):
if (a, b) == (0, 0):
raise ValueError("a and b cannot be both 0")
if b == 0:
return (1, 0, a)
x, y, g = extended_euclidean(b, a % b)
return (y, x - a // b * y, g)
def calc_inverse_element(a, n):
x, _, gcd = extended_euclidean(a, n)
if gcd == 1:
return x % n
return None
# ===================== Pygame 初始化 =====================
pygame.init()
WIDTH, HEIGHT = 700, 480
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Inverse Element Calculator")
# 颜色配置
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (220, 220, 220)
BLUE = (50, 100, 200)
RED = (200, 50, 50)
GREEN = (50, 180, 50)
# 字体
font_input = pygame.font.SysFont("Arial", 36)
font_text = pygame.font.SysFont("Arial", 32)
font_result = pygame.font.SysFont("Arial", 38)
font_equation = pygame.font.SysFont("Arial", 32)
font_btn = pygame.font.SysFont("Arial", 34)
# ===================== 布局:全部水平居中,对称美观 =====================
# 输入框组居中
INPUT_X = WIDTH // 2 - 110 # 输入框水平居中
INPUT_WIDTH = 220
INPUT_HEIGHT = 50
# 输入a
input_a_rect = pygame.Rect(INPUT_X, 110, INPUT_WIDTH, INPUT_HEIGHT)
input_a_text = ""
a_active = False
# 输入n
input_n_rect = pygame.Rect(INPUT_X, 180, INPUT_WIDTH, INPUT_HEIGHT)
input_n_text = ""
n_active = False
# 按钮居中
btn_rect = pygame.Rect(WIDTH // 2 - 150, 260, 300, 60)
btn_text = "Calculate"
# 结果
result_text = ""
equation_text = ""
result_color = BLACK
# ===================== 主循环 =====================
running = True
while running:
screen.fill(WHITE)
# -------------------- 文字与输入框(居中对齐)--------------------
# Input a 标签(与输入框垂直居中)
label_a = font_text.render("Input a:", True, BLACK)
screen.blit(label_a, (INPUT_X - 120, 118))
# Input n 标签(与输入框垂直居中)
label_n = font_text.render("Input n:", True, BLACK)
screen.blit(label_n, (INPUT_X - 120, 188))
# 绘制输入框a
pygame.draw.rect(screen, BLUE if a_active else GRAY, input_a_rect, 2)
screen.blit(font_input.render(input_a_text, True, BLACK), (input_a_rect.x+10, input_a_rect.y+5))
# 绘制输入框n
pygame.draw.rect(screen, BLUE if n_active else GRAY, input_n_rect, 2)
screen.blit(font_input.render(input_n_text, True, BLACK), (input_n_rect.x+10, input_n_rect.y+5))
# 绘制按钮(居中)
pygame.draw.rect(screen, BLUE, btn_rect, border_radius=8)
btn_surface = font_btn.render(btn_text, True, WHITE)
screen.blit(btn_surface, (btn_rect.centerx - btn_surface.get_width()//2, btn_rect.centery - btn_surface.get_height()//2))
# 结果文字(居中显示,更美观)
result_surface = font_result.render(result_text, True, result_color)
screen.blit(result_surface, (WIDTH // 2 - result_surface.get_width()//2, 350))
# 公式提示(底部居中)
formula = font_equation.render(equation_text, True, BLACK)
screen.blit(formula, (WIDTH // 2 - formula.get_width()//2, 420))
# ===================== 事件处理 =====================
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 鼠标点击
if event.type == pygame.MOUSEBUTTONDOWN:
if input_a_rect.collidepoint(event.pos):
a_active = True
n_active = False
elif input_n_rect.collidepoint(event.pos):
n_active = True
a_active = False
elif btn_rect.collidepoint(event.pos):
result_text = ""
try:
a = int(input_a_text)
n = int(input_n_text)
if a <= 0 or n <= 0:
result_text = "ERROR: Please input positive integers!"
result_color = RED
else:
inv = calc_inverse_element(a, n)
if inv is not None:
result_text = f"x: {inv}"
result_color = GREEN
equation_text = f"{a} * {inv} ≡ 1 (mod {n})"
else:
result_text = f"ERROR: {a} and {n} are not relatively prime, no inverse exists!"
result_color = RED
except ValueError:
result_text = "ERROR: Please input valid integers!"
result_color = RED
else:
a_active = False
n_active = False
# 键盘输入
if event.type == pygame.KEYDOWN:
if a_active:
if event.key == pygame.K_BACKSPACE:
input_a_text = input_a_text[:-1]
elif event.unicode.isdigit():
input_a_text += event.unicode
if n_active:
if event.key == pygame.K_BACKSPACE:
input_n_text = input_n_text[:-1]
elif event.unicode.isdigit():
input_n_text += event.unicode
pygame.display.flip()
pygame.quit()
请将完整的程序保存为 inverse_element_calculator.py。使用如下的命令可以运行 inverse_element_calculator.py
bash
python3 inverse_element_calculator.py
初始界面如下图所示 ⬇️

此时需要用户输入 a 和 n。我输入了 34 和 23,然后点击 Calculate 按钮,就可以计算 a 对应的逆元了 ⬇️

由于扩展欧几里得算法很高效,所以即使输入比较大 a 和 n,也可以立刻算出结果,示例效果如下 ⬇️ ( a=63245986,n=102334155)
