拡張ユークリッドの互除法・モジュラ逆数
モジュラ逆数の計算
(サンプルコードでは主に
モジュラ逆数
ここでは、3つの方法でのモジュラ逆数の求め方を紹介します。
- オイラーの定理を用いる方法 (
が素数である場合、m の素因数分解が既知である場合 )m - 変則的な拡張ユークリッド互除法を用いる方法 ( 一般の自然数
に対する場合 )m - ニュートン・ラフソン法を用いる方法 (
が累乗数である場合 )m
m が素数、もしくは m の素因数分解が既知である場合のモジュラ逆数 (オイラーの定理)
オイラーのトーシェント関数
n^{\varphi(m)}\equiv 1\ (\operatorname{mod}m) \gcd(n,m)=1\quad\Longrightarrow\quad n^{-1}\equiv n^{\varphi(m)-1}\ (\operatorname{mod}m)
オイラーのトーシェント関数
オイラーのトーシェント関数
-
が素数であれば、m \varphi(m)=m-1 -
が素数ではなく、m の素因数分解が以下のように (m は互いに異なる素数 ) 既知であれば、p_1,p_2,p_3,\dots,p_d
サンプルコード (Rust)
// モジュラ逆数 $n^{-1} \mod 998244353$ をオイラーの定理から計算 (998244353 は素数)
pub fn invmod_euler_998244353(n: u32) -> Option<u32> {
const MOD: u32 = 998_244_353;
let nm = n % MOD;
if nm == 0 {
// n が m の倍数なら gcd(n,m) != 1 なので、モジュラ逆数は存在しない
return None;
}
// 内部計算には64bit整数を使用(変数r,変数s)
let (mut r, mut s, mut t) = (1u64, u64::from(nm), MOD - 2);
// 繰り返し二乗法(バイナリ法)にて $n^{M-2}$ を計算
while t > 0 {
if t % 2 == 1 {
r = (r * s) % u64::from(MOD);
}
s = (s * s) % u64::from(MOD);
t /= 2;
}
// 32ビット整数に変換して出力
Some(r as u32)
}
m に関するモジュラ逆数 (変則・拡張ユークリッド互除法)
一般の 拡張ユークリッドの互除法によるモジュラ逆数の計算は、多くの資料では符号付き整数型を用いた実装が多いですが、ここでは少し式を工夫して符号無し整数型を用いた実装を考えてみます。
符号付き整数型を用いた拡張ユークリッド互除法のコード例 (Rust):
// extgcd(a, b) -> (g, x, y)
// ax + by = gcd(a,b) = g を満たす整数の組 (g, x, y) をひとつ返す
pub fn extgcd(a: i64, b: i64) -> (i64, i64, i64) {
match b {
0 => (a, 1, 0),
_ => {
let (d, y, x) = extgcd(b, a % b);
(d, x, y - (a / b) * x)
},
}
}
対象
このような形の連立方程式を変形する操作を考えてみます。
(a_in-c_im)/2^{s_i}=x_i (b_in-d_im)/2^{s_i}=-y_i
目標
\displaystyle x_i=1\quad\Longrightarrow\quad a_in\equiv 2^{s_i}\quad(\operatorname{mod}m) \quad\Longrightarrow\quad n^{-1}\equiv a_i/2^{s_i}\quad(\operatorname{mod}m) \displaystyle y_i=1\quad\Longrightarrow\quad b_in\equiv -2^{s_i}\quad(\operatorname{mod}m) \quad\Longrightarrow\quad n^{-1}\equiv -b_i/2^{s_i}\quad(\operatorname{mod}m)
\displaystyle x_i=1\quad\Longrightarrow\quad a_in\equiv 1\quad(\operatorname{mod}m) \quad\Longrightarrow\quad n^{-1}\equiv a_i\quad(\operatorname{mod}m) \displaystyle y_i=1\quad\Longrightarrow\quad b_in\equiv -1\quad(\operatorname{mod}m) \quad\Longrightarrow\quad n^{-1}\equiv -b_i\quad(\operatorname{mod}m)
制約
\{n,m,a_i,b_i,c_i,d_i,s_i,t_i,x_i,y_i\} は非負整数 \gcd(n,m)=1
初期値
(a_0n-c_0m)/2^{s_0}=x_0=(1\cdot n-0\cdot m)/1=n (b_0n-d_0m)/2^{s_0}=-y_0=(0\cdot n-1\cdot m)/1=-m
式変形パターン
A.
(a_{i+1}n-c_{i+1}m)/2^{s_{i+1}}=x_{i+1}=((a_i+b_it_i)n-(c_i+d_it_i)m)/2^{s_i}=x_i-t_iy_i (b_{i+1}n-d_{i+1}m)/2^{s_{i+1}}=-y_{i+1}=(b_in-d_im)/2^{s_i}=-y_i
B.
(a_{i+1}n-c_{i+1}m)/2^{s_{i+1}}=x_{i+1}=(a_in-c_im)/2^{s_i}=x_i (b_{i+1}n-d_{i+1}m)/2^{s_{i+1}}=-y_{i+1}=((a_it_i+b_i)n-(c_it_i+d_i)m)/2^{s_i}=t_ix_i-y_i
C.
(a_{i+1}n-c_{i+1}m)/2^{s_{i+1}}=x_{i+1}=(a_in-c_im)/2^{s_i+t_i}=x_i/2^{t_i} (b_{i+1}n-d_{i+1}m)/2^{s_{i+1}}=-y_{i+1}=(2^{t_i}b_in-2^{t_i}d_im)/2^{s_i+t_i}=-y_i
D.
(a_{i+1}n-c_{i+1}m)/2^{s_{i+1}}=x_{i+1}=(2^{t_i}a_in-2^{t_i}c_im)/2^{s_i+t_i}=x_i (b_{i+1}n-d_{i+1}m)/2^{s_{i+1}}=-y_{i+1}=(b_in-d_im)/2^{s_i+t_i}=-y_i/2^{t_i}
m が 3 以上の奇数 の場合 )
式変形手順の具体例 ( 同じ mod でのモジュラ逆数の計算を多数行う想定で調整した例です。実行環境、言語環境、問題の条件などによって最適な手順ではない場合もあるかと思います。
- 例えば
の場合、0\le n\lt m\lt 2^{32} の\lbrace 2^0, 2^{-1}, 2^{-2}, ..., 2^{-63}\rbrace をそれぞれ事前計算しておく ( 計算方法は後述の補題参照、\operatorname{mod}m が取りうる値くらいの数だけ用意 )\lfloor\log_2 mn\rfloor -
と初期化 ((a,b,s,x,y)=(1,0,0,n,m) は使わない )c,d -
であれば異常終了 :x=0 のためモジュラ逆数が存在しない\gcd(n,m)=y\ge 3 -
の偶数であればx\ge 2 を奇数にするような 自然数x にて 式変形C. を実行t -
であれば正常終了 :x=1 を計算して出力\displaystyle n^{-1}\equiv 2^{-s}a\quad(\operatorname{mod}m) -
であれば異常終了 :x=y のためモジュラ逆数が存在しない\gcd(n,m)=x=y\ge 3 -
であればx\lt 8y として 式変形B. を実行t=\lfloor y/x\rfloor -
であればx\lt y として式変形B. を実行t=1 -
であれば異常終了 :y=0 のためモジュラ逆数が存在しない\gcd(n,m)=x\ge 3 -
の偶数であればy\ge 2 を奇数にするような 自然数y にて 式変形D. を実行t -
であれば正常終了 :y=1 を計算して出力\displaystyle n^{-1}\equiv -2^{-t}b\quad(\operatorname{mod}m) -
であれば8x\gt y として 式変形A. を実行t=\lfloor x/y\rfloor -
であればx\gt y として式変形A. を実行t=1 - 手順 3. に戻る
m が 3 以上の奇数の場合の 2 の累乗数に対するモジュラ逆数の事前計算
補題: 1/2^0\ \operatorname{mod}m=1 \displaystyle 1/2^{q+1}\ \operatorname{mod}m=\begin{cases}(1/2^{q}\ \operatorname{mod}m)/2&((1/2^{q}\ \operatorname{mod}m)\equiv 0\ \operatorname{mod}2)\\ ((1/2^{q}\ \operatorname{mod}m)+m)/2&((1/2^{q}\ \operatorname{mod}m)\equiv 1\ \operatorname{mod}2)\end{cases}
例:
m が 2 以上の偶数 の場合 )
式変形手順の具体例 ( -
と初期化 ((a,b,x,y)=(1,0,n,m) は使わない )c,d,s -
であれば異常終了 :x=0 のためモジュラ逆数が存在しない\gcd(n,m)=y\ge 2 -
であれば正常終了 :x=1 を計算して出力\displaystyle n^{-1}\equiv a\quad(\operatorname{mod}m) -
であればx\le y として 式変形B. を実行t=\lfloor y/x\rfloor -
であれば異常終了 :y=0 のためモジュラ逆数が存在しない\gcd(n,m)=x\ge 2 -
であれば正常終了 :y=1 を計算して出力\displaystyle n^{-1}\equiv -b\quad(\operatorname{mod}m) -
であればx\ge y として 式変形A. を実行t=\lfloor x/y\rfloor - 手順 2. に戻る
m=1 の場合
-
より、nn^{-1} \equiv 0 \equiv 1\ (\operatorname{mod}1) を出力n^{-1}\equiv 0\ (\operatorname{mod}1)
m=0 の場合
- 未定義動作
サンプルコード (Rust)
// モジュラ演算用定数構造体
pub struct ModBin {
modulus: u32,
im: u64,
ib: [u32; 64],
}
impl ModBin {
pub fn new(modulus: u32) -> Self {
assert_ne!(modulus, 0);
// `im` $= \lceil (2^{64} / M) \rceil = \lfloor (2^{64}-1) / M\rfloor + 1$
let im = (!0u64) / u64::from(modulus) + 1;
let mut ib = [0u32; 64];
ib[0] = 1;
if modulus % 2 != 0 {
// `modulus` が奇数の時は、 $\{2^0,2^{-1},...,2^{-63}\}$ を事前計算
for i in 1..64 {
let prev = ib[i - 1];
if prev % 2 == 0 {
ib[i] = prev / 2;
} else {
ib[i] = (prev / 2) + (modulus / 2) + 1;
}
}
}
Self { modulus, im, ib }
}
pub fn add(&self, a: u32, b: u32) -> u32 {
let v = u64::from(a) + u64::from(b);
match v.overflowing_sub(u64::from(self.modulus)) {
(_, true) => v as u32,
(w, false) => w as u32,
}
}
pub fn sub(&self, a: u32, b: u32) -> u32 {
match a.overflowing_sub(b) {
(v, true) => v.wrapping_add(self.modulus),
(v, false) => v,
}
}
pub fn neg(&self, a: u32) -> u32 {
self.sub(0, a)
}
pub fn mul(&self, a: u32, b: u32) -> u32 {
// Barrett Reduction
let z = u64::from(a) * u64::from(b);
let x = ((u128::from(z) * u128::from(self.im)) >> 64) as u64;
match z.overflowing_sub(x.wrapping_mul(u64::from(self.modulus))) {
(v, true) => (v as u32).wrapping_add(self.modulus),
(v, false) => v as u32,
}
}
pub fn div(&self, a: u32, b: u32) -> Option<u32> {
self.inv_bingcd(b).map(|ib| self.mul(a, ib))
}
// モジュラ逆数の計算(バイナリGCD法無しの実装例)
pub fn inv_plain(&self, n: u32) -> Option<u32> {
let m = self.modulus;
assert_ne!(m, 0);
let (mut a, mut b, mut x, mut y) = (1, 0, n, m);
if m == 1 {
return Some(0);
}
loop {
match x {
// $\gcd(n,m) = y \ge 2$
0 => return None,
// $n^{-1} \equiv a$
1 => return Some(a),
_ => {},
}
b += a * (y / x);
y %= x;
match y {
// $\gcd(n,m) = x \ge 2$
0 => return None,
// $n^{-1} \equiv -b$
1 => return Some(m - b),
_ => {},
}
a += b * (x / y);
x %= y;
}
}
// モジュラ逆数の計算(modが奇数の場合、バイナリGCD法のハイブリッド)
pub fn inv_bingcd(&self, n: u32) -> Option<u32> {
let m = self.modulus;
assert_ne!(m, 0);
let (mut a, mut b, mut x, mut y) = (1, 0, n, m);
if m == 1 {
return Some(0);
} else if m % 2 == 0 {
// m が偶数: 拡張ユークリッド互除法
loop {
match x {
// $\gcd(n,m) = y \ge 2$
0 => return None,
// $n^{-1} \equiv a$
1 => return Some(a),
_ => {},
}
b += a * (y / x);
y %= x;
match y {
// $\gcd(n,m) = x \ge 2$
0 => return None,
// $n^{-1} \equiv -b$
1 => return Some(m - b),
_ => {},
}
a += b * (x / y);
x %= y;
}
} else {
// m が奇数: バイナリGCD + 拡張ユークリッド互除法
if n == 0 {
// $\gcd(n,m) = m \ge 3$
return None;
}
// 式変形C: $t = \operatorname{tzcnt}(x)
let mut s = x.trailing_zeros();
x >>= s;
loop {
if x == 1 {
// $n^{-1} \equiv a / 2^{s}$
return Some(self.mul(a, self.ib[s as usize]));
}
if x == y {
// $\gcd(n,m) = x = y \ge 3$
return None;
}
if x < (y >> 3) {
// 式変形B: $t = \lfloor y / x \rfloor$
b += a * (y / x);
y %= x;
if y == 0 {
// $\gcd(n,m) = x \ge 3$
return None;
}
// 式変形D: $t = \operatorname{tzcnt}(y)$
let t = y.trailing_zeros();
y >>= t;
a <<= t;
s += t;
} else {
while x < y {
// 式変形B: $t = 1$
b += a;
y -= x;
// 式変形D: $t = \operatorname{tzcnt}(y)$
let t = y.trailing_zeros();
y >>= t;
a <<= t;
s += t;
}
}
if y == 1 {
// $n^{-1} \equiv -b / 2^{s}$
return Some(self.mul(m - b, self.ib[s as usize]));
}
if (x >> 3) > y {
// 式変形A: $t = \lfloor x / y \rfloor$
a += b * (x / y);
x %= y;
if x == 0 {
// $\gcd(n,m) = y \ge 3$
return None;
}
// 式変形C: $t = \operatorname{tzcnt}(x)$
let t = x.trailing_zeros();
x >>= t;
b <<= t;
s += t;
} else {
while x > y {
// 式変形A: $t = 1$
a += b;
x -= y;
// 式変形C: $t = \operatorname{tzcnt}(x)$
let t = x.trailing_zeros();
x >>= t;
b <<= t;
s += t;
}
}
}
}
}
}
m が累乗数の場合のモジュラ逆数 (ニュートン・ラフソン法)
自然数
と計算する事ができます。また、
となります。
証明:
また、 1回の反復で有効桁数を2倍ではなく3倍にする方法もあります。
証明:
m が 2 の累乗数の場合
例: x_0=n\quad\Longrightarrow\quad nx_0\equiv 1\ (\operatorname{mod}2^3) x_1=x_0(2-nx_0)\mod 2^q\quad\Longrightarrow\quad nx_1\equiv 1\ (\operatorname{mod}2^{\min(6,q)}) x_2=x_1(2-nx_1)\mod 2^q\quad\Longrightarrow\quad nx_2\equiv 1\ (\operatorname{mod}2^{\min(12,q)}) x_3=x_2(2-nx_2)\mod 2^q\quad\Longrightarrow\quad nx_3\equiv 1\ (\operatorname{mod}2^{\min(24,q)}) x_4=x_3(2-nx_3)\mod 2^q\quad\Longrightarrow\quad nx_4\equiv 1\ (\operatorname{mod}2^{\min(48,q)}) x_5=x_4(2-nx_4)\mod 2^q\quad\Longrightarrow\quad nx_5\equiv 1\ (\operatorname{mod}2^{\min(96,q)})
のように反復計算を必要な回数 (
また、以下のように計算する桁数を反復の初期でケチる事もできます。
x_0=n\quad\Longrightarrow\quad nx_0\equiv 1\ (\operatorname{mod}2^3) x_1=x_0(2-nx_0)\mod 2^4\quad\Longrightarrow\quad nx_1\equiv 1\ (\operatorname{mod}2^4) x_2=x_1(2-nx_1)\mod 2^8\quad\Longrightarrow\quad nx_2\equiv 1\ (\operatorname{mod}2^8) x_3=x_2(2-nx_2)\mod 2^{16}\quad\Longrightarrow\quad nx_3\equiv 1\ (\operatorname{mod}2^{16}) x_4=x_3(2-nx_3)\mod 2^{32}\quad\Longrightarrow\quad nx_4\equiv 1\ (\operatorname{mod}2^{32}) x_5=x_4(2-nx_4)\mod 2^{64}\quad\Longrightarrow\quad nx_5\equiv 1\ (\operatorname{mod}2^{64})
サンプルコード (Rust)
// モジュラ逆数 $n^{-1} \mod 2^{32}$ の計算
pub fn invmod_u32(n: u32) -> Option<u32> {
// n が偶数だとモジュラ逆数は存在しない
if n % 2 == 0 {
return None;
}
// 初期値は n 自身
let mut x = n;
for _ in 0..4 {
// $x := x * (2 - (n * x)) \mod 2^{32}$ を4回繰り返す
x = x.wrapping_mul(2u32.wrapping_sub(n.wrapping_mul(x)));
}
// できあがり。
Some(x)
}
// モジュラ逆数 $n^{-1} \mod 2^{64}$ の計算
pub fn invmod_u64(n: u64) -> Option<u64> {
// n が偶数だとモジュラ逆数は存在しない
if n % 2 == 0 {
return None;
}
// 初期値は n 自身
let mut x = n;
for _ in 0..5 {
// $x := x * (2 - (n * x)) \mod 2^{64}$ を5回繰り返す
x = x.wrapping_mul(2u64.wrapping_sub(n.wrapping_mul(x)));
}
// できあがり。
Some(x)
}
極力、(記述するプログラムコードの上では)符号なし整数型を用いた演算による拡張ユークリッドの互除法により、モジュラ逆数の計算を目指す。
互いに素な正の整数
例として
-
を求める100^{-1}\operatorname{\ mod\ }998244353 -
を満たす 整数100x-998244353y=1 の組において、x,y が最小の正の整数となるものを求めるx
拡張ユークリッドの互除法とベズーの等式と一次不定方程式:
この項目では、拡張ユークリッドの互除法における係数の操作の説明として、行列の積による表現を用いている。
除算・剰余算による拡張ユークリッドの互除法
演算回数の最悪ケース:
- フィボナッチ数列 (OEIS000045,
~F_{0} の数表) の隣接項、例えばF_{2000} を係数に用いた、F_{43}=433494437, F_{44}=701408733 の拡張ユークリッド互除法433494437x-701408733y=1
- 縦ベクトルの絶対値が小さな方が0ではない間、絶対値の大きな方を、小さな方で割り算し、割り算の商を用いた操作によって割り算の余りに置き換える操作を繰り返す。
- 縦ベクトルの絶対値が小さな方が0になったら、必要な補正を行った上で出力を行う。縦ベクトルの絶対値が0でない方
=\gcd(a,b)
-
の時、x=828542813, y=83 を満たす。100x-998244353y=1
除算・剰余算による拡張ユークリッドの互除法のPythonコード例
def extgcd(a, b):
x, y, z, w = 1, 0, 0, 1
while True:
if a == 0: return x - y, z - w, b
y, w, b = y + (b // a) * x, w + (b // a) * z, b % a
if b == 0: return x, z, a
x, z, a = x + (a // b) * y, z + (a // b) * w, a % b
import math
m = 998244353
for i in range(105):
# i * x - m * y = g; 0 <= x <= m/g; -1 <= y <= i/g; g = gcd(i, m)
# if i == 0 then y == -1, else y >= 0
x, y, g = extgcd(i, m)
print(i, x, y, g, i * x % m)
0 1 -1 998244353 0
1 1 0 1 1
2 499122177 1 1 1
3 332748118 1 1 1
4 748683265 3 1 1
5 598946612 3 1 1
6 166374059 1 1 1
7 855638017 6 1 1
8 873463809 7 1 1
9 443664157 4 1 1
10 299473306 3 1 1
11 272248460 3 1 1
12 582309206 7 1 1
13 460728163 6 1 1
14 926941185 13 1 1
15 865145106 13 1 1
16 935854081 15 1 1
17 939524097 16 1 1
18 720954255 13 1 1
19 105078353 2 1 1
20 149736653 3 1 1
21 617960790 13 1 1
22 136124230 3 1 1
23 217009642 5 1 1
24 291154603 7 1 1
25 319438193 8 1 1
26 729486258 19 1 1
27 480636170 13 1 1
28 962592769 27 1 1
29 481911067 14 1 1
30 432572553 13 1 1
31 128805723 4 1 1
32 967049217 31 1 1
33 756245722 25 1 1
34 968884225 33 1 1
35 370776474 13 1 1
36 859599304 31 1 1
37 242816194 9 1 1
38 551661353 21 1 1
39 486324172 19 1 1
40 573990503 23 1 1
41 97389693 4 1 1
42 308980395 13 1 1
43 510729669 22 1 1
44 68062115 3 1 1
45 288381702 13 1 1
46 108504821 5 1 1
47 191153174 9 1 1
48 644699478 31 1 1
49 692659347 34 1 1
50 658841273 33 1 1
51 313174699 16 1 1
52 364743129 19 1 1
53 828731161 44 1 1
54 240318085 13 1 1
55 54449692 3 1 1
56 980418561 55 1 1
57 700522353 40 1 1
58 740077710 43 1 1
59 727534020 43 1 1
60 715408453 43 1 1
61 654586461 40 1 1
62 563525038 35 1 1
63 205986930 13 1 1
64 982646785 63 1 1
65 890741115 58 1 1
66 378122861 25 1 1
67 432075914 29 1 1
68 983564289 67 1 1
69 405084665 28 1 1
70 185388237 13 1 1
71 702988981 50 1 1
72 429799652 31 1 1
73 382888245 28 1 1
74 121408097 9 1 1
75 771975633 58 1 1
76 774952853 59 1 1
77 894530654 69 1 1
78 243162086 19 1 1
79 404352143 32 1 1
80 786117428 63 1 1
81 825708292 67 1 1
82 547817023 45 1 1
83 60135202 5 1 1
84 653612374 55 1 1
85 387553690 33 1 1
86 754487011 65 1 1
87 493385140 43 1 1
88 533153234 47 1 1
89 392568004 35 1 1
90 144190851 13 1 1
91 65818309 6 1 1
92 553374587 51 1 1
93 42935241 4 1 1
94 95576587 9 1 1
95 819611153 78 1 1
96 322349739 31 1 1
97 699800165 68 1 1
98 845451850 83 1 1
99 584830025 58 1 1
100 828542813 83 1 1
101 889524671 90 1 1
102 655709526 67 1 1
103 38766771 4 1 1
104 681493741 71 1 1
extgcd
関数が負の値を返している(モジュラ逆数と最大公約数だけを求めたい場合は計算を省略しても良い部分)。符号なし整数型でこの手法による拡張ユークリッドの互除法を実装する際には、この
拡張ユークリッドの互除法による乗法逆元(除算・剰余算)
from typing import Tuple, Optional
def mod_inv_extgcd(n: int, m: int) -> Tuple[Optional[int], int]:
# extended euclidean algorithm
# return (n^{-1} mod m) or None
x, y, a, b = 1, 0, n, m
assert a >= 0 and b >= 0
while True:
if a == 0:
if b == 1: # b = gcd(n,m) = 1
return x - y, b
else: # b = gcd(n,m) != 1
return None, b
y, b = y + (b // a) * x, b % a
if b == 0:
if a == 1: # a = gcd(n,m) = 1
return x, a
else: # a = gcd(n,m) != 1
return None, a
x, a = x + (a // b) * y, a % b
m = 998244353
for i in range(105):
inv, g = mod_inv_extgcd(i, m)
if inv is None:
print(i, g, inv)
else:
print(i, g, inv, i * inv % m)
拡張ユークリッドの互除法による乗法逆元(バイナリ法1)
from typing import Tuple, Optional
def mod_inv_binextgcd(n: int, m: int) -> Tuple[Optional[int], int]:
x, y, a, b, s = 1, 0, n, m, 0
# n * x mod m = 1 となる 整数x を返す
# mが偶数である場合、mが負である場合、nが負である場合は動作未定義
assert (m & 1) == 1 and m > 0 and n >= 0
if a == 0:
return None, b
while (a & 1) == 0:
a, s = (a>>1), (s+1)
while True:
if a == 1: # a = gcd(n,m) = 1
# n^{-1} mod m = x * 2^{-s} mod m
for _ in range(s):
if (x & 1) == 0:
x = (x >> 1)
else:
x = ((x + m) >> 1)
return x, a
if a == b: # a = b = gcd(n,m) != 1
return None, a
while a < b:
y, b = (x+y), (b-a)
while (b & 1) == 0:
x, b, s = (x<<1), (b>>1), (s+1)
if b == 1: # b = gcd(n,m) = 1
# n^{-1} mod m = -y * 2^{-s} mod m
for _ in range(s):
if (y & 1) == 0:
y = (y >> 1)
else:
y = ((y + m) >> 1)
return m - y, b
while a > b:
x, a = (x+y), (a-b)
while (a & 1) == 0:
y, a, s = (y<<1), (a>>1), (s+1)
m = 998244353
for i in range(105):
inv, g = mod_inv_binextgcd(i)
if inv is None:
print(i, g, inv)
else:
print(i, g, inv, i * inv % m)
拡張ユークリッドの互除法による乗法逆元(バイナリ法2)
from typing import Tuple, Optional
class OddModulo:
def __init__(self, m: int) -> None:
self.m = m
# must be odd modulo
assert m & 1 == 1 and m > 0
# b = [2^{0} mod m, 2^{-1} mod m, ...]
b = [1]
for _ in range(m.bit_length() * 2):
if (b[-1] & 1) == 0:
b.append(b[-1] // 2)
else:
b.append((b[-1] + m) // 2)
self.b = b
def mod_inv_binextgcd(self, n: int) -> Tuple[Optional[int], int]:
# binary extended euclidean algorithm
x, y, a, b, s = 1, 0, n, self.m, 0
assert a >= 0
if a == 0:
return None, b
while (a & 1) == 0:
a, s = (a>>1), (s+1)
if a == 1: # a = gcd(n,m) = 1
# n^{-1} mod m = x * 2**-s mod m
return x * self.b[s] % self.m, a
if a < (b >> 9):
# b が a に比べてかなり大きい場合は
# 除算・剰余算を使うメリットのが大きいので使っておく
y, b = (y + x * (b // a)), (b % a)
if b == 0:
return None, a
while (b & 1) == 0:
x, b, s = (x << 1), (b >> 1), (s + 1)
while True:
if a == 1: # a = gcd(n,m) = 1
# n^{-1} mod m = x * 2**-s mod m
return x * self.b[s] % self.m, a
if a == b: # a = b = gcd(n,m) != 1
# modular inverse does not exist.
return None, a
while a < b:
y, b = (y + x), (b - a)
while (b & 1) == 0:
x, b, s = (x << 1), (b >> 1), (s + 1)
if b == 1: # b = gcd(n,m) = 1
# n^{-1} mod m = -y * 2^{-s} mod m
return self.m - y * self.b[s] % self.m, b
while a > b:
x, a = (x + y), (a - b)
while (a & 1) == 0:
y, a, s = (y << 1), (a >> 1), (s + 1)
m = 998244353
modulo = OddModulo(m)
for i in range(105):
inv, g = modulo.mod_inv_binextgcd(i)
if inv is None:
print(i, g, inv)
else:
print(i, g, inv, i * inv % m)
Python 3.8 以降の場合、モジュラ逆数の計算は組み込み関数 pow(base, exp[, mod])
で出来ます。
(AtCoderで使われている PyPy3 7.3.0 だと Python 3.6 相当のためまだ対応していない)
Python (3.8.2) import sys;print(sys.version)
3.8.2 (default, Feb 26 2020, 02:56:10)
[GCC 7.4.0]
PyPy3 (7.3.0) import sys;print(sys.version)
3.6.9 (7.3.0+dfsg-1~ppa1~ubuntu18.04, Dec 24 2019, 08:12:19)
[PyPy 7.3.0 with GCC 7.4.0]
# Python 3.8以降
n = 100
m = 998244353
inv = pow(n, -1, m)
print(n, m, inv, n * inv % m)
100 998244353 828542813 1
フェルマーの小定理
-
が整数でn が素数の場合、m n^{m} \equiv n \mod m -
が整数でn が素数でm の場合、\gcd(n,m)=1 n^{m-1} \equiv 1 \mod m -
が整数でn が素数でm の場合、\gcd(n,m)=1 n^{m-2} \equiv n^{-1} \mod m
# Python 3.8以降
n = 100
m = 998244353
inv = pow(n, m-2, m)
print(n, m, inv, n * inv % m)
100 998244353 828542813 1
行列の積の結合法則
- 行列・縦ベクトルの外側にある矢印は、行列の積の計算時に要素を見る順番の方向を表したもの(行列の積の説明用であり、通常はこの矢印は書かない)
- 行列の内側にある点線は、行列の要素の区切りを示したもの(説明用であり、通常はあまりこの区切り線は書かない)
- 「1行2列の行ベクトル
」と「2行2列の行列A 」の積 → 「1行2列の行ベクトル」(「B 列目の結果」は 「j の列ベクトル」と「A の$j$列目の列ベクトル」との内積)B - 「2行2列の行列
」と「2行1列の列ベクトルA 」の積 → 「2行1列の列ベクトル」(「B 行目の結果」は「i のA 行目の行ベクトル」と「i の列ベクトル」との内積)B - 「2行2列の行列
」と「2行2列の行列A 」の積 → 「2行2列の行列」(「B 行i 列目の結果」は「j のA 行目の行ベクトル」と「i のB 列目の列ベクトル」との内積)j
バイナリGCDアルゴリズム(主に加減算・シフト演算)によるモジュラ逆数
演算回数の最悪ケース?例:
-
のバイナリGCDアルゴリズム12297829382473034410x-12297829382473034411y=1 -
のバイナリGCDアルゴリズム(2^{64}-2^{63})x-(2^{64}-1)y=1 -
のバイナリGCDアルゴリズム(2^{64}-2^{62})x-(2^{64}-3)y=1 - もっと回数が多いケースが存在するかもしれないが、行列操作で
回を上回ることはない(絶対値を減らすように行う減算と2の累乗での除算を交互に行うので、この回数を上回る事ができない)2\log_2 ab
- 片方、もしくは両方が
であれば、結果を出力する0 - 片方の絶対値が
になるか、双方が同じ絶対値になるまでの間、 次の2つの操作(偶数を奇数にする操作・引き算する操作)を繰り返す1
a. 偶数があれば、それを の累乗 で割って奇数にする操作2
b. ともに奇数であれば、絶対値の大きな方をもう一方の数で引き算する操作 - 片方の絶対値が
になる (1 ) か、双方が同じ絶対値 (\gcd(a,b)/\gcd(a,b,2^s)=1 ) になった場合は、=\gcd(a,b)/\gcd(a,b,2^s) の累乗 で割った操作の補正を必要に応じて行った上で、結果を出力する2
- 非負整数
および 正奇数a において、m を満たす 非負整数2h\equiv a\ (\operatorname{mod}m) はこのように求められる:h
h=\begin{cases}a/2&\left(a\equiv 0 \left(\operatorname{mod}2\right)\right)\\(a+m)/2&\left(a\equiv 1 \left(\operatorname{mod}2\right)\right)\\\end{cases} - これを繰り返し適用する事で、
を求める事ができる。116006912 \times 2^{33} \equiv 1 \mod 998244353
1/2^n を事前計算する場合の最終結果の求め方
-
より、116006912 \times 445267313 \equiv 828542813 \mod 998244353 の時、x=828542813 を満たす 整数100x-998244353y=1 が存在する。y
x/2^n を事後計算する場合の最終結果の求め方
除算による拡張ユークリッド互除法とBinaryGCD法の混合によるモジュラ逆数の計算
1/2^n を事前計算する場合の最終結果の求め方
x/2^n を事後計算する場合の最終結果の求め方
バイナリGCDアルゴリズムの変形(基数3)
- 片方、もしくは両方が
であれば、結果を出力する0 - 片方の絶対値が
になるか、双方が同じ絶対値になるまでの間、 次の2つの操作(偶数を奇数にする操作・引き算する操作)を繰り返す1
a. 3の倍数があれば、それを の累乗 で割って 3の倍数でない数 にする操作3
b. ともに3の倍数でなければ、絶対値の大きな方をもう一方の数で足し算もしくは引き算して、3の倍数にする操作 - 片方の絶対値が
になる (1 ) か、双方が同じ絶対値 (\gcd(a,b)/\gcd(a,b,3^s)=1 ) になった場合は、=\gcd(a,b)/\gcd(a,b,3^s) の累乗 で割った操作の補正を必要に応じて行った上で、結果を出力する3