<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Prime Numbers on kenji.blog</title><link>http://kenji.blog/tags/prime-numbers/</link><description>Recent content in Prime Numbers on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ja</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 16:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/tags/prime-numbers/index.xml" rel="self" type="application/rss+xml"/><item><title>リーマン予想と素数の分布：現代暗号技術との深い関係</title><link>http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/</link><pubDate>Fri, 11 Sep 2026 16:00:00 +0900</pubDate><guid>http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/</guid><description>&lt;img src="http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/img/eyecatch.jpg" alt="Featured image of post リーマン予想と素数の分布：現代暗号技術との深い関係" />&lt;h1 id="1-はじめに素数という宇宙の神秘とリーマン予想">1. はじめに：素数という宇宙の神秘とリーマン予想
&lt;/h1>&lt;p>「素数（Prime Numbers）」は、1と自分自身でしか割り切れない自然数であり、数学の世界における「原子」とも呼ばれます。2, 3, 5, 7, 11, 13&amp;hellip; と続くこの数列は、一見すると無秩序でランダムに現れるように見えます。古代ギリシャの数学者ユークリッドが「素数が無限に存在すること」を証明して以来、数え切れないほどの数学者たちがこの素数の並びに潜む規則性を解き明かそうと挑んできました。&lt;/p>
&lt;p>その素数の謎に最も肉薄したのが、1859年にドイツの数学者ベルンハルト・リーマン（Bernhard Riemann）が提唱した**「リーマン予想（Riemann Hypothesis）」**です。リーマン予想は、現代数学において最も重要かつ未解決の難問の一つであり、クレイ数学研究所が定めるミレニアム懸賞問題の一つとして100万ドルの賞金が懸けられています。&lt;/p>
&lt;p>一見すると、素数の分布に関する純粋数学の難問は、私たちの日常生活とは無縁に思えるかもしれません。しかし、現代社会のインフラを支えるインターネットのセキュリティ、特に&lt;strong>RSA暗号や楕円曲線暗号（ECC）といった現代暗号技術&lt;/strong>は、巨大な素数の性質に深く依存しています。&lt;/p>
&lt;p>本記事では、素数の分布から素数定理、リーマンゼータ関数、そしてリーマン予想の核心へと至る数学的な旅をし、それがどのようにして現代暗号技術と結びついているのか、そしてもしリーマン予想が証明されたら世界はどうなるのかについて、極めて詳細かつ深く掘り下げて解説します。&lt;/p>
&lt;hr>
&lt;h1 id="2-素数定理と素数の分布ガウスの発見">2. 素数定理と素数の分布：ガウスの発見
&lt;/h1>&lt;p>素数がどのように分布しているのかを理解するために、数学者たちは「ある数 $x$ 以下の素数がいくつ存在するか」を表す&lt;strong>素数計数関数（Prime-counting function）&lt;/strong> $\pi(x)$ を考えました。&lt;/p>
&lt;p>例えば：&lt;/p>
&lt;ul>
&lt;li>$\pi(10) = 4$ （2, 3, 5, 7）&lt;/li>
&lt;li>$\pi(100) = 25$&lt;/li>
&lt;li>$\pi(1000) = 168$&lt;/li>
&lt;/ul>
&lt;p>15歳の天才数学者カール・フリードリヒ・ガウス（Carl Friedrich Gauss）は、膨大な素数の表を計算し、素数の出現頻度が自然対数 $\ln x$ に反比例して減少していくことを見出しました。つまり、ある数 $x$ の付近で素数が見つかる確率は約 $\frac{1}{\ln x}$ であると予想したのです。&lt;/p>
&lt;p>これを積分を用いて表現したものが、&lt;strong>対数積分（Logarithmic integral）&lt;/strong> $\text{Li}(x)$ です。&lt;/p>
$$ \text{Li}(x) = \int_{2}^{x} \frac{dt}{\ln t} $$
&lt;p>ガウスの予想は、後に1896年にジャック・アダマールとシャルル＝ジャン・ド・ラ・ヴァレ・プーサンによって独立に証明され、**素数定理（Prime Number Theorem, PNT）**として確立されました。&lt;/p>
$$ \lim_{x \to \infty} \frac{\pi(x)}{\text{Li}(x)} = 1 $$
&lt;p>または近似的に以下のように表されます。&lt;/p>
$$ \pi(x) \sim \frac{x}{\ln x} $$
&lt;p>この定理により、素数は巨視的に見れば非常に滑らかで予測可能な分布を持っていることがわかりました。しかし、微視的に見れば $\pi(x)$ と $\text{Li}(x)$ の間には常に「誤差」すなわち「揺らぎ」が存在します。この揺らぎの正体こそが、リーマン予想が解き明かそうとしている最大のミステリーなのです。&lt;/p>
&lt;hr>
&lt;h1 id="3-リーマンゼータ関数とオイラー積">3. リーマンゼータ関数とオイラー積
&lt;/h1>&lt;p>素数の分布を解析する上で最強の武器となるのが**リーマンゼータ関数（Riemann Zeta Function）**です。もともとはレオンハルト・オイラー（Leonhard Euler）によって実数 $s > 1$ に対して定義された無限級数でした。&lt;/p>
$$ \zeta(s) = \sum_{n=1}^\infty \frac{1}{n^s} = 1 + \frac{1}{2^s} + \frac{1}{3^s} + \frac{1}{4^s} + \dots $$
&lt;p>オイラーの最大の功績の一つは、この無限級数が、すべての素数 $p$ に関する無限積として表せることを証明したことです。これが**オイラー積（Euler Product Formula）**です。&lt;/p>
$$ \zeta(s) = \prod_{p \text{ prime}} \frac{1}{1 - p^{-s}} = \left( \frac{1}{1 - 2^{-s}} \right) \left( \frac{1}{1 - 3^{-s}} \right) \left( \frac{1}{1 - 5^{-s}} \right) \dots $$
&lt;p>証明の直感的な理解は、右辺の各項を等比級数として展開し、それらを掛け合わせると、算術の基本定理（すべての自然数は素数の積として一意に表される）により、左辺の自然数の逆数和が完全に再構築されるというものです。&lt;/p>
&lt;p>&lt;strong>このたった一つの数式が、解析学（無限級数・連続関数）と数論（素数・離散的な数）を繋ぐ架け橋となりました。&lt;/strong> ゼータ関数を調べることは、素数の分布を調べることと同義なのです。&lt;/p>
&lt;hr>
&lt;h1 id="4-解析接続と複素平面への拡張">4. 解析接続と複素平面への拡張
&lt;/h1>&lt;p>リーマンの天才性は、オイラーが実数のみで考えていた $\zeta(s)$ の変数 $s$ を、**複素数 $s = \sigma + it$（$\sigma$ は実部、$t$ は虚部）**に拡張したことにあります。&lt;/p>
&lt;p>もともとの無限級数は $\sigma > 1$ でしか収束しませんが、リーマンは「解析接続（Analytic Continuation）」という手法を用いて、$s = 1$ の極を除外した全複素平面上で $\zeta(s)$ が意味を持つように定義を拡張しました。&lt;/p>
&lt;p>彼はさらに、ゼータ関数が満たす美しい関数等式（Functional equation）を導き出しました。&lt;/p>
$$ \zeta(s) = 2^s \pi^{s-1} \sin\left(\frac{\pi s}{2}\right) \Gamma(1-s) \zeta(1-s) $$
&lt;p>ここで $\Gamma(x)$ はガンマ関数です。この等式により、右半平面の性質から左半平面の性質を知ることができます。&lt;/p>
&lt;h3 id="零点zeros-of-the-zeta-function">零点（Zeros of the Zeta Function）
&lt;/h3>&lt;p>ゼータ関数の値が 0 になる複素数 $s$ を「零点」と呼びます。
関数等式から、$s$ が負の偶数（$-2, -4, -6, \dots$）のとき、$\sin(\pi s / 2)$ が 0 になるため、$\zeta(s) = 0$ となります。これらは**自明な零点（Trivial zeros）**と呼ばれます。&lt;/p>
&lt;p>しかし、素数の分布において重要なのは、それ以外の零点、すなわち $0 \le \sigma \le 1$ の「臨界領域（Critical strip）」に存在する**非自明な零点（Non-trivial zeros）**です。&lt;/p>
&lt;hr>
&lt;h1 id="5-リーマン予想の核心と明示公式">5. リーマン予想の核心と明示公式
&lt;/h1>&lt;p>リーマンは、少数の零点を計算し、ある驚くべき予想を立てました。これが&lt;strong>リーマン予想&lt;/strong>です。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>リーマン予想 (Riemann Hypothesis)&lt;/strong>
リーマンゼータ関数 $\zeta(s)$ の非自明な零点はすべて、実部が $1/2$ の直線上（$\text{Re}(s) = 1/2$）に存在する。&lt;/p>
&lt;/blockquote>
&lt;p>この実部が1/2の直線を「臨界線（Critical line）」と呼びます。&lt;/p>
&lt;div class="mermaid">graph TD
A["リーマンゼータ関数 ζ(s)"] --> B["解析接続による複素数平面への拡張"]
B --> C["自明な零点 (s = -2, -4, -6 ...)"]
B --> D["非自明な零点 (0 &lt;= Re(s) &lt;= 1)"]
D --> E["リーマン予想"]
E --> F["すべての非自明な零点は Re(s) = 1/2 上にある"]
F --> G["素数分布の誤差項の極限の証明へ"]&lt;/div>
&lt;p>なぜリーマン予想がそれほど重要なのでしょうか？それは、ゼータ関数の零点が素数の分布を&lt;strong>完全に&lt;/strong>決定しているからです。&lt;/p>
&lt;p>リーマンと後の数学者フォン・マンゴルトは、素数の分布を正確に記述する「明示公式（Explicit formula）」を導きました。チェビシェフ関数 $\psi(x)$ を用いると、以下のように表されます。&lt;/p>
$$ \psi(x) = x - \sum_{\rho} \frac{x^\rho}{\rho} - \ln(2\pi) - \frac{1}{2}\ln(1 - x^{-2}) $$
&lt;p>ここで $\rho$ はゼータ関数の非自明な零点をすべてわたる和です。
主項は $x$ （これは素数定理に対応）であり、そこから零点 $\rho$ に依存する波のような項を足し引きすることで、素数の階段状の正確な分布が復元されるのです。非自明な零点は、素数の分布の「周波数（波）」を表していると言えます。&lt;/p>
&lt;p>もしリーマン予想が正しく、すべての非自明な零点 $\rho$ の実部がちょうど $1/2$ であるならば、素数定理の誤差項は理論上考えうる最小の範囲に収まることになります。&lt;/p>
$$ |\pi(x) - \text{Li}(x)| \le \frac{1}{8\pi} \sqrt{x} \ln x \quad \text{for} \quad x \ge 2657 $$
&lt;p>つまり、&lt;strong>リーマン予想が真であれば、素数は私たちが想像し得る限り最も「規則正しく、美しく」分布している&lt;/strong>ことが証明されるのです。&lt;/p>
&lt;hr>
&lt;h1 id="6-現代暗号技術と素数の不可分な関係">6. 現代暗号技術と素数の不可分な関係
&lt;/h1>&lt;p>ここまでは深遠な純粋数学の世界でしたが、この素数の性質は現代のデジタル社会を根底から支えています。その代表が&lt;strong>RSA暗号&lt;/strong>をはじめとする公開鍵暗号方式です。&lt;/p>
&lt;p>インターネットでのクレジットカード決済、パスワードの送信、ブロックチェーンの電子署名など、あらゆる通信の安全性は「素数」に依存しています。&lt;/p>
&lt;h3 id="rsa暗号の仕組み">RSA暗号の仕組み
&lt;/h3>&lt;p>RSA暗号の安全性は、「桁数の大きい合成数の素因数分解は非常に困難である」という数学的事実（素因数分解問題）に基づいています。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>鍵生成&lt;/strong>:
巨大な素数 $p$ と $q$ （例えばそれぞれ2048ビット）をランダムに選びます。
それらを掛け合わせて $N = p \times q$ を計算します。この $N$ が公開鍵の一部となります。
オイラーのトーティエント関数 $\phi(N) = (p-1)(q-1)$ を用いて、秘密鍵 $d$ を生成します。&lt;/p>
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
&lt;/li>
&lt;li>
&lt;p>&lt;strong>暗号化と復号&lt;/strong>:
平文 $M$ は、公開鍵 $e, N$ を用いて暗号文 $C$ に変換されます。
&lt;/p>
$$ C \equiv M^e \pmod{N} $$
&lt;p>
秘密鍵 $d$ を持つ者だけが、復号できます。
&lt;/p>
$$ M \equiv C^d \pmod{N} $$
&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph LR
A["平文 (Plaintext)"] --> B["公開鍵(e, N)で暗号化"]
B --> C["暗号文 (Ciphertext)"]
C --> D["秘密鍵(d)で復号"]
D --> E["元の平文"]
F["攻撃者 (Attacker)"] -- "Nの素因数分解を試みる" --> C
F -.-> G["pとqが分からないとdは計算不能"]&lt;/div>
&lt;p>RSA暗号を破るためには、巨大な $N$ から元の素数 $p$ と $q$ を見つけ出す（素因数分解する）必要があります。現在主流のアルゴリズム（一般数体篩法：GNFSなど）を用いても、何百桁もの数を素因数分解するには、スーパーコンピュータを使っても宇宙の年齢をはるかに超える時間がかかるとされています。&lt;/p>
&lt;hr>
&lt;h1 id="7-リーマン予想が暗号技術に与えるインパクト">7. リーマン予想が暗号技術に与えるインパクト
&lt;/h1>&lt;p>では、純粋数学の頂点にある「リーマン予想」と「暗号技術」はどのように交差するのでしょうか？&lt;/p>
&lt;h3 id="71-素数生成アルゴリズム素数判定と拡張リーマン予想grh">7.1. 素数生成アルゴリズム（素数判定）と拡張リーマン予想（GRH）
&lt;/h3>&lt;p>RSA暗号を運用するためには、最初に巨大な素数 $p$ と $q$ を生成する必要があります。しかし、「ある数が素数かどうか」を確実かつ高速に判定するのは簡単ではありません。&lt;/p>
&lt;p>現在、実用的に使われているのは**ミラー・ラビン素数判定法（Miller-Rabin primality test）**という確率的アルゴリズムです。このアルゴリズムは高速ですが、極めて低い確率で合成数を素数と誤判定する「擬素数」のリスクがあります。&lt;/p>
&lt;p>しかし、リーマン予想をディリクレのL関数に拡張した**「拡張リーマン予想（Generalized Riemann Hypothesis, GRH）」&lt;strong>が真であると仮定すると、話は劇的に変わります。
GRHが真であれば、ミラー・ラビン判定法におけるテスト回数の上限が数学的に保証され、確率的アルゴリズムから&lt;/strong>「決定性多項式時間アルゴリズム」へと昇華**するのです（これは、AKS素数判定法が発見される以前から知られていた重大な事実でした）。&lt;/p>
&lt;p>つまり、リーマン予想（およびその拡張）は、「巨大な素数を絶対の自信を持って高速に生成できるか」という暗号の基盤生成に直接的なお墨付きを与える役割を持っています。&lt;/p>
&lt;h3 id="72-素因数分解アルゴリズムとの関係">7.2. 素因数分解アルゴリズムとの関係
&lt;/h3>&lt;p>暗号を解読する側のアルゴリズム（一般数体篩法など）の計算量を評価する際にも、素数の分布に関する知識が不可欠です。素因数分解アルゴリズムの多くは「滑らかな数（Smooth numbers：小さな素因数しか持たない数）」の分布に依存しています。&lt;/p>
&lt;p>滑らかな数がどの程度の頻度で現れるかを厳密に評価するためには、素数の分布に関する深い理解が必要であり、ここでもゼータ関数やリーマン予想に直結する解析的整数論のテクニックが駆使されています。リーマン予想が証明され、素数分布の誤差が完全に決定されれば、素因数分解アルゴリズムの性能限界もより正確に見極めることが可能になります。&lt;/p>
&lt;hr>
&lt;h1 id="8-もしリーマン予想が証明されたら暗号は破られるのか">8. もしリーマン予想が証明されたら、暗号は破られるのか？
&lt;/h1>&lt;p>都市伝説のように「リーマン予想が解けたらRSA暗号は一瞬で崩壊する」と語られることがありますが、&lt;strong>これは数学的には不正確&lt;/strong>です。&lt;/p>
&lt;p>リーマン予想の証明自体が、直ちに素因数分解を劇的に高速化する魔法のアルゴリズムを生み出すわけではありません。リーマン予想はあくまで素数の「巨視的な分布の規則性」についての定理であり、個別の数 $N$ がどの素数で割り切れるか（局所的な性質）を直接教えてくれるものではないからです。&lt;/p>
&lt;p>しかし、影響はゼロではありません。
リーマン予想が証明される過程で、&lt;strong>「新たな数学的ツール」や「未知の解析手法」が発見される可能性&lt;/strong>が極めて高いからです。歴史を見ても、フェルマーの最終定理やポアンカレ予想が証明された際、その過程で開発された新しい理論が数学全体を大きく飛躍させました。&lt;/p>
&lt;p>もしリーマンゼータ関数の零点の性質を完全に操作できる未知の代数幾何学的手法や、非可換幾何の手法が確立されれば、それが結果として素因数分解の画期的なアルゴリズム（例えば、計算量を多項式時間に落とし込むような古典アルゴリズム）の発見に繋がる可能性は否定できません。その意味で、暗号学者はリーマン予想の動向から決して目を離すことができないのです。&lt;/p>
&lt;h3 id="量子コンピュータとショアのアルゴリズム">量子コンピュータとショアのアルゴリズム
&lt;/h3>&lt;p>暗号技術にとってより直接的で現実的な脅威は、リーマン予想の証明ではなく&lt;strong>量子コンピュータ&lt;/strong>です。1994年にピーター・ショア（Peter Shor）が発表した「ショアのアルゴリズム」は、十分な性能を持つ量子コンピュータがあれば、素因数分解を多項式時間で解けることを証明しました。これにより、RSA暗号や楕円曲線暗号は根本的に破られることになります。&lt;/p>
&lt;p>現在、世界中で量子コンピュータでも解読できない「耐量子計算機暗号（Post-Quantum Cryptography, PQC）」への移行（格子暗号など）が進められています。素数に依存した暗号技術は、ある意味で黄金期を終えようとしているのかもしれませんが、素数そのものの数学的価値が失われることは永遠にありません。&lt;/p>
&lt;hr>
&lt;h1 id="9-結び数学の抽象性と現実社会の交差点">9. 結び：数学の抽象性と現実社会の交差点
&lt;/h1>&lt;div class="mermaid">graph TD
A["純粋数学の探求"] --> B["リーマン予想の解明"]
B --> C["素数分布の完全な理解"]
C --> D["数論・代数幾何学の飛躍的発展"]
D -.-> E["新しい素因数分解アルゴリズムの可能性"]
E -.-> F["暗号技術の安全性評価の更新"]
A --> G["応用数学・計算機科学"]
G --> H["素数判定・暗号生成の効率化"]
H --> F&lt;/div>
&lt;p>古代ギリシャから続く素数への飽くなき探求は、リーマンという天才によって複素数平面上の美しいシンフォニー（ゼータ関数の零点）へと昇華されました。そして驚くべきことに、その純粋無垢な数学の結晶が、数世紀の時を経てインターネット社会の安全を担保する最強の盾として応用されています。&lt;/p>
&lt;p>リーマン予想は、数学が持つ「抽象的な美しさ」と「物理世界・現実社会への驚異的な適用力」を同時に象徴する存在です。&lt;/p>
&lt;p>未だ誰も頂上に到達していないこの巨大な数学の山がいつの日か征服されたとき、私たちは素数という宇宙の真理を完全に理解するとともに、情報化社会の基盤に対して新たな視点を持つことになるでしょう。暗号技術を学ぶことは、そのまま人類の英知の歴史を辿る旅でもあるのです。&lt;/p></description></item><item><title>C++で実装する高速な素数判定アルゴリズム（Miller-Rabin法など）</title><link>http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/</link><pubDate>Fri, 11 Sep 2026 14:00:00 +0900</pubDate><guid>http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/</guid><description>&lt;img src="http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/img/eyecatch.jpg" alt="Featured image of post C++で実装する高速な素数判定アルゴリズム（Miller-Rabin法など）" />&lt;h1 id="はじめになぜ高速な素数判定が必要なのか">はじめに：なぜ高速な素数判定が必要なのか
&lt;/h1>&lt;p>情報科学や暗号理論、あるいは競技プログラミングの世界において、「ある数が素数であるかどうか」を高速かつ正確に判定することは、非常に重要で基礎的な課題です。例えば、現代のインターネット社会のセキュリティを支えているRSA暗号などの公開鍵暗号方式は、巨大な素数の生成とその掛け算の困難性（素因数分解の難しさ）を安全性の根拠としています。したがって、巨大な数が素数であるかを瞬時に見分ける技術は、デジタル社会の根幹を支える技術と言っても過言ではありません。&lt;/p>
&lt;p>また、競技プログラミング（AtCoderやCodeforcesなど）においても、素数判定は頻出のテーマです。制約が $N \le 10^{18}$ といった巨大な入力に対して、1秒以内で素数判定を数万回行うようなシチュエーションでは、伝統的で素朴なアルゴリズムでは到底計算時間（Time Limit Exceeded: TLE）に間に合いません。&lt;/p>
&lt;p>本記事では、素朴な素数判定アルゴリズムから始まり、確率的素数判定法である「フェルマーテスト」、そしてその弱点を克服した実用上最強レベルの高速アルゴリズム「ミラー・ラビン（Miller-Rabin）素数判定法」について、数学的な背景からC++による高度に最適化された実装まで、徹底的に解説します。特に、64ビット整数（$N &lt; 2^{64}$）に対しては、確率的な判定にとどまらず「100%確実に素数判定ができる（決定論的判定）」手法についても詳しく説明し、実践でそのまま使えるC++のソースコードを提供します。&lt;/p>
&lt;hr>
&lt;h1 id="1-素数判定の基礎と試し割り法-trial-division">1. 素数判定の基礎と試し割り法 (Trial Division)
&lt;/h1>&lt;p>素数（Prime number）とは、1と自分自身以外に正の約数を持たない、2以上の自然数のことです。素数の定義に忠実に従えば、ある整数 $N$ が素数かどうかを判定するには、$2$ から $N-1$ までのすべての整数で $N$ を割ってみて、一度も割り切れなければ素数、一度でも割り切れれば合成数（素数でない）と判断できます。&lt;/p>
&lt;p>しかし、この方法は計算量が $O(N)$ となり、$N$ が $10^{18}$ などの巨大な数の場合、現代のコンピュータでも計算に膨大な時間がかかってしまいます。&lt;/p>
&lt;h2 id="試し割り法の最適化sqrtn-までの探索">試し割り法の最適化：$\sqrt{N}$ までの探索
&lt;/h2>&lt;p>合成数 $N$ が $a \times b = N$ （$a \le b$）と表されるとき、必ず $a \le \sqrt{N}$ となります。したがって、素数判定のループは $N-1$ まで回す必要はなく、$\sqrt{N}$ まで調べれば十分です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 試し割り法による素数判定 (O(sqrt(N)))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">is_prime_trial_division&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 3以上の奇数のみ調べる
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>このアルゴリズムの計算量は $O(\sqrt{N})$ です。$N \le 10^{12}$ 程度であれば一瞬で計算できますが、$N \approx 10^{18}$ の場合、ループ回数が約 $10^9$ 回となり、C++の処理系であっても数百ミリ秒から数秒の時間を要するため、複数回の判定には不向きです。&lt;/p>
&lt;hr>
&lt;h1 id="2-フェルマーテスト確率的素数判定の幕開け">2. フェルマーテスト：確率的素数判定の幕開け
&lt;/h1>&lt;p>試し割り法の限界を突破するために考え出されたのが、数論の定理を用いた「確率的アルゴリズム（Probabilistic Algorithm）」です。その代表例が、フェルマーの小定理を利用した「フェルマーテスト（Fermat Primality Test）」です。&lt;/p>
&lt;h2 id="フェルマーの小定理-fermats-little-theorem">フェルマーの小定理 (Fermat&amp;rsquo;s Little Theorem)
&lt;/h2>&lt;p>ピエール・ド・フェルマー（Pierre de Fermat）が発見したこの定理は、次のように主張しています。&lt;/p>
&lt;blockquote>
&lt;p>任意の素数 $p$ と、互いに素な（$p$ の倍数ではない）任意の整数 $a$ について、以下の合同式が成り立つ。
&lt;/p>
$$ a^{p-1} \equiv 1 \pmod p $$
&lt;/blockquote>
&lt;p>この定理の対偶をとると、「ある整数 $N$ と、$N$ と互いに素な整数 $a$ に対して $a^{N-1} \not\equiv 1 \pmod N$ となるならば、$N$ は確実に合成数である」と言えます。この性質を利用して、判定対象の数 $N$ に対してランダムな底（base）$a$ を選び、$a^{N-1} \pmod N$ を計算して $1$ になるか確認するのがフェルマーテストです。&lt;/p>
&lt;h2 id="高速なべき乗剰余繰り返し二乗法">高速なべき乗剰余（繰り返し二乗法）
&lt;/h2>&lt;p>フェルマーテストを行うためには、$a^{N-1} \pmod N$ という巨大なべき乗を高速に計算する必要があります。これには「繰り返し二乗法（Modular Exponentiation / Binary Exponentiation）」を用います。計算量は $O(\log N)$ となり、非常に高速です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 繰り返し二乗法を用いた a^b mod m の計算
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="nf">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128_t&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">res&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128_t&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>※ ここではオーバーフローを防ぐため、GCC/Clangの拡張である &lt;code>__int128_t&lt;/code>（128ビット整数）を使用して途中の積を保持しています。&lt;/p>
&lt;h2 id="疑似素数とカーマイケル数-carmichael-numbers">疑似素数とカーマイケル数 (Carmichael Numbers)
&lt;/h2>&lt;p>フェルマーテストは非常に強力ですが、致命的な弱点があります。それは、$N$ が合成数であるにもかかわらず、すべての $a$ （$N$ と互いに素な $a$）に対して $a^{N-1} \equiv 1 \pmod N$ が成立してしまうような数が存在するということです。&lt;/p>
&lt;p>このような数を「絶対疑似素数」または「カーマイケル数」と呼びます。最小のカーマイケル数は $561 = 3 \times 11 \times 17$ です。
カーマイケル数が存在するため、フェルマーテストだけでは「確率100%」の決定論的判定を行うことができません。いくら異なる $a$ を試しても、$561$ のような数は常に素数のフリ（騙し）をしてしまいます。&lt;/p>
&lt;hr>
&lt;h1 id="3-ミラーラビン素数判定法-miller-rabin-primality-test">3. ミラー・ラビン素数判定法 (Miller-Rabin Primality Test)
&lt;/h1>&lt;p>フェルマーテストの弱点（カーマイケル数の存在）を見事に克服したのが、ミラー（Gary L. Miller）とラビン（Michael O. Rabin）によって考案された「ミラー・ラビン素数判定法」です。
現在、実用的な高速素数判定アルゴリズムとして、さまざまなプログラミング言語の内部ライブラリや暗号システムの鍵生成で最も広く使われています。&lt;/p>
&lt;h2 id="数学的原理">数学的原理
&lt;/h2>&lt;p>ミラー・ラビンのアルゴリズムは、フェルマーの小定理に加えて、「素数を法とする剰余体（$\mathbb{Z}/p\mathbb{Z}$）においては、$x^2 \equiv 1 \pmod p$ の解は $x \equiv 1$ または $x \equiv -1$ に限られる」という性質を利用します（合成数を法とする場合は、これ以外の非自明な平方根が存在し得ます）。&lt;/p>
&lt;p>判定したい奇数 $N$ から $1$ を引いた $N-1$ は必ず偶数になります。そこで、$N-1$ を $2$ で割れるだけ割り、次のような形式で表します。
&lt;/p>
$$ N-1 = d \cdot 2^s $$
&lt;p>
（ここで、$d$ は奇数、$s \ge 1$）&lt;/p>
&lt;p>任意の底 $a$ （$1 &lt; a &lt; N-1$）について、$a^{N-1} \equiv 1 \pmod N$ であるかをフェルマーの小定理に従って検証しますが、その計算を段階的に行います。
具体的には、$a^d, a^{d \cdot 2}, a^{d \cdot 4}, \ldots, a^{d \cdot 2^s}$ と順番に2乗を繰り返していきます。&lt;/p>
&lt;p>ミラー・ラビンテストが $N$ を「素数である（あるいは強い確率で素数である）」と判定するための条件は、以下の &lt;strong>いずれか&lt;/strong> が成立することです。&lt;/p>
&lt;ol>
&lt;li>$a^d \equiv 1 \pmod N$&lt;/li>
&lt;li>ある $r$ （$0 \le r &lt; s$）が存在して、$a^{d \cdot 2^r} \equiv -1 \pmod N$ が成り立つ。
※ C++のモジュロ演算では $-1 \pmod N$ は $N-1$ となります。&lt;/li>
&lt;/ol>
&lt;p>もし $N$ が素数であれば、任意の $a$ についてこの条件が必ず満たされます。逆に $N$ が合成数であれば、ランダムな $a$ を選んだとき、この条件を満たす確率（騙される確率）は $\frac{1}{4}$ 以下であることが数学的に証明されています。
$k$ 回の独立したテストを行えば、誤判定の確率は $\left(\frac{1}{4}\right)^k$ 以下となり、実用上ゼロとみなすことができます。カーマイケル数のような「絶対にごまかせる」数は存在しません。&lt;/p>
&lt;h2 id="ミラーラビン法のアルゴリズムの流れmermaidフローチャート">ミラー・ラビン法のアルゴリズムの流れ（Mermaidフローチャート）
&lt;/h2>&lt;p>以下の図は、ミラー・ラビン素数判定法の1回のテスト（1つの底 $a$ に対するテスト）の論理的なフローを示しています。&lt;/p>
&lt;div class="mermaid">graph TD
Start["テスト開始 (入力: N, a)"] --> CalcDS["N-1 = d * 2^s を満たす d (奇数) と s を計算"]
CalcDS --> CalcX["x = a^d mod N を計算"]
CalcX --> CheckX1{"x == 1 または x == N-1 ?"}
CheckX1 -- "Yes" --> ReturnTrue["素数の可能性がある (Probably Prime)"]
CheckX1 -- "No" --> LoopStart["r = 1 から s-1 までループ開始"]
LoopStart --> LoopCondition{"r &lt; s ?"}
LoopCondition -- "No" --> ReturnFalse["確実に合成数 (Composite)"]
LoopCondition -- "Yes" --> SquareX["x = (x * x) mod N を計算"]
SquareX --> CheckXMinus1{"x == N - 1 ?"}
CheckXMinus1 -- "Yes" --> ReturnTrue
CheckXMinus1 -- "No" --> CheckXOne{"x == 1 ?"}
CheckXOne -- "Yes" --> ReturnFalse
CheckXOne -- "No" --> LoopNext["r を 1 増加させて次へ"]
LoopNext --> LoopCondition&lt;/div>
&lt;hr>
&lt;h1 id="4-64ビット整数に対する決定論的判定">4. 64ビット整数に対する決定論的判定
&lt;/h1>&lt;p>ミラー・ラビン素数判定法は本来「確率的」アルゴリズムですが、$N$ の上限が固定されている場合、特定の複数の $a$ （底）をすべて試すことで、「100%確実に」素数判定を行うことができます。
これを &lt;strong>決定論的ミラー・ラビンテスト（Deterministic Miller-Rabin Test）&lt;/strong> と呼びます。&lt;/p>
&lt;p>Jim Sinclairらの研究により、$N &lt; 2^{64}$ （約 $1.8 \times 10^{19}$）のすべての整数に対しては、以下の $7$ つの素数を底 $a$ として選んでテストすれば、十分かつ完全に決定論的な判定が可能であることが判明しています。&lt;/p>
&lt;p>&lt;strong>テストすべき底 $a$ のリスト:&lt;/strong>
&lt;code>{2, 325, 9375, 28178, 450775, 9780504, 1795265022}&lt;/code>&lt;/p>
&lt;p>あるいは、よく知られた別のセットとして、以下の $12$ 個の素数を使用することでも $N &lt; 2^{64}$ 以下で完璧に判定できます。
&lt;code>{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}&lt;/code>&lt;/p>
&lt;p>今回は、アルゴリズムの簡潔さと信頼性を高めるために、後者の $12$ 個の素数をベース（あるいはより最適化された $7$ つのベース）として用いる手法を採用します。C++の実装では、条件分岐で範囲を分けることで、テスト回数を最小限に抑える最適化を行います。&lt;/p>
&lt;hr>
&lt;h1 id="5-cによる高度な実装-highly-optimized-c-implementation">5. C++による高度な実装 (Highly Optimized C++ Implementation)
&lt;/h1>&lt;p>それでは、ここまでの数学的な理論とアルゴリズムの設計をまとめ上げ、現代のC++における最強レベルのミラー・ラビン素数判定関数の実装コードを提示します。&lt;/p>
&lt;h2 id="実装のポイント">実装のポイント
&lt;/h2>&lt;ol>
&lt;li>
&lt;p>&lt;strong>64ビット整数の掛け算のオーバーフロー回避:&lt;/strong>
$N \approx 10^{18}$ の場合、モジュロ乗算における $x \times x$ は最大 $10^{36}$ となり、通常の64ビット整数（&lt;code>uint64_t&lt;/code> や &lt;code>long long&lt;/code>）の最大値 $1.8 \times 10^{19}$ を軽々とオーバーフローしてしまいます。
この問題を解決するため、GCCやClangの拡張型である &lt;code>__int128_t&lt;/code>（あるいは &lt;code>unsigned __int128&lt;/code>）を使用し、128ビット精度で計算を行った後にモジュロを取ります。これにより、複雑なアルゴリズムを使わずとも高速に剰余乗算が可能です。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>決定論的ベースの選択:&lt;/strong>
$N$ の値が小さい場合は、少しの底（base）をテストするだけで済むように最適化します。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="完全なcソースコード">完全なC++ソースコード
&lt;/h2>&lt;p>以下に、実戦投入可能な完成版のソースコードを示します。このコードは競技プログラミング等の環境にそのままコピーして利用可能です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;span class="lnt">90
&lt;/span>&lt;span class="lnt">91
&lt;/span>&lt;span class="lnt">92
&lt;/span>&lt;span class="lnt">93
&lt;/span>&lt;span class="lnt">94
&lt;/span>&lt;span class="lnt">95
&lt;/span>&lt;span class="lnt">96
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cstdint&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;initializer_list&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 128ビット整数による高速な (a * b) mod m
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">inline&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="p">)((&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="n">__int128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 繰り返し二乗法による (base^exp) mod m の計算
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">exp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ミラー・ラビン素数判定法による 64bit 整数の決定論的判定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">is_prime_miller_rabin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 境界値と小さな素数の事前判定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// n-1 = d * 2^s の形に分解する
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">d&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 判定に使用する底 (bases) のリスト
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// N の大きさに応じてテストするベースの数を最小限に抑える最適化
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">4759123141ULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">61&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1122004669633ULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">23&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1662803&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// N &amp;lt; 2^64 のすべての数に対して決定論的となる 7 つの底
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">325&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9375&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">28178&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">450775&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9780504&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1795265022&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 各底についてテストを実行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nl">a&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// a が n の倍数の場合は判定不能だが素数ではない
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 第一条件クリア、次の底へ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">composite&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// s-1 回のループ (x = x^2 mod n)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">composite&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 第二条件クリア、素数の可能性あり
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// どの条件も満たさなければ確実に合成数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">composite&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// すべてのベースで条件をクリアした場合は確実に素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// テスト用のサンプル
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">test_cases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1000000007&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 有名な素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">998244353&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 有名な素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">1000000000000000003&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 10^18近辺の素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">1000000000000000007&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 合成数 (10^18 + 7)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">561&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// カーマイケル数 (合成数)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">18446744073709551557ULL&lt;/span> &lt;span class="c1">// 2^64 近辺の最大の素数の一つ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nl">n&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">test_cases&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">is_prime_miller_rabin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s">&amp;#34;Prime&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s">&amp;#34;Composite&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h1 id="6-アルゴリズムの計算量とパフォーマンス評価">6. アルゴリズムの計算量とパフォーマンス評価
&lt;/h1>&lt;p>実装したアルゴリズムの性能について考察します。&lt;/p>
&lt;h2 id="時間計算量-time-complexity">時間計算量 (Time Complexity)
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>試し割り法:&lt;/strong> $O(\sqrt{N})$&lt;/li>
&lt;li>&lt;strong>フェルマーテスト:&lt;/strong> べき乗計算 $O(\log N) \times k$ （$k$ は試行回数）&lt;/li>
&lt;li>&lt;strong>ミラー・ラビン法:&lt;/strong> べき乗計算とループ $O(\log N) \times k$&lt;/li>
&lt;/ul>
&lt;p>64ビット環境（$N \le 2^{64}$）において、上記の決定論的ミラー・ラビン法は、最大でも $7$ つのベースしか検証しません。したがって、$k \le 7$ の定数とみなすことができ、全体の時間計算量は厳密に $O(\log N)$ となります。
最大ケース（$N \approx 10^{19}$）であっても、実行ステップ数は高々 $7 \times 64 = 448$ ステップの基本演算に収まり、実行時間は数マイクロ秒（$10^{-6}$ 秒）以下です。試し割り法の $O(\sqrt{N})$ （ループ回数 $\approx 4 \times 10^9$ 回）と比較すると、&lt;strong>数百万倍の高速化&lt;/strong> が達成されています。&lt;/p>
&lt;h2 id="さらなる最適化モンゴメリ乗算-montgomery-multiplication">さらなる最適化：モンゴメリ乗算 (Montgomery Multiplication)
&lt;/h2>&lt;p>本記事の実装では、128ビット整数の拡張型 &lt;code>__int128_t&lt;/code> を用いて除算（モジュロ演算 &lt;code>%&lt;/code>）を行っています。現代のCPUであっても、整数除算（DIV命令）は加算や乗算に比べて数十サイクルを要するコストの高い命令です。&lt;/p>
&lt;p>より極限の最適化を追求するライブラリ作成者や競技プログラマは、&lt;strong>モンゴメリ乗算（Montgomery Multiplication）&lt;/strong> と呼ばれる手法を採用することがあります。モンゴメリ乗算は、数値を特殊な「モンゴメリ空間」に写像することで、高コストなモジュロ演算（除算）を「ビットシフトと乗算のみ」に置き換える驚異的なアルゴリズムです。
これをミラー・ラビン判定の剰余乗算に組み込むことで、さらに実行速度を2倍から3倍程度引き上げることが可能です。これについては非常に奥深いテーマとなるため、別の記事で詳細に解説したいと思います。&lt;/p>
&lt;hr>
&lt;h1 id="7-まとめ">7. まとめ
&lt;/h1>&lt;p>本記事では、素数判定の基礎から発展的な内容までを一挙に解説しました。
ポイントを振り返りましょう。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>試し割り法&lt;/strong> は確実だが、計算量 $O(\sqrt{N})$ のため $N$ が $10^{12}$ を超えると実用性に欠ける。&lt;/li>
&lt;li>&lt;strong>フェルマーテスト&lt;/strong> は $O(\log N)$ と非常に高速だが、カーマイケル数などの絶対疑似素数に騙されるという致命的な弱点がある。&lt;/li>
&lt;li>&lt;strong>ミラー・ラビン素数判定法&lt;/strong> は、フェルマーテストの弱点を解消した実用的で最強のアルゴリズム。&lt;/li>
&lt;li>C++による実装では、&lt;code>__int128_t&lt;/code> を活用することで 64ビット整数の乗算オーバーフローを安全に処理できる。&lt;/li>
&lt;li>64ビット整数の範囲内（$N &lt; 2^{64}$）であれば、$7$ 個または $12$ 個の特定の素数を底として選ぶことで、確率的ではなく&lt;strong>決定論的（100%正確）に素数判定が可能&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;p>高速な素数判定は、巨大な数を扱う計算において避けては通れない技術です。本記事で提供したC++のミラー・ラビンのソースコードは、そのまま実戦で活用できる堅牢なものになっています。ぜひ、ご自身のプロジェクトやアルゴリズム競技で活用してみてください。&lt;/p>
&lt;div class="mermaid">graph LR
TrialDivision["試し割り法 (O(√N))"] --> Fermat["フェルマーテスト (O(log N), 弱点あり)"]
Fermat --> MillerRabin["ミラー・ラビン法 (O(log N), 決定論的化可能)"]
MillerRabin --> Montgomery["+ モンゴメリ乗算 (定数倍高速化)"]
style MillerRabin fill:#f9f,stroke:#333,stroke-width:2px&lt;/div>
&lt;p>プログラミングと数学が交差するアルゴリズムの世界は非常に美しく、奥深いものです。今後の学習の一助となれば幸いです。&lt;/p>
&lt;hr>
&lt;p>&lt;em>Reference:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;em>Pomerance, C., Selfridge, J. L., &amp;amp; Wagstaff, S. S. (1980). The pseudoprimes to 25.10^9. Mathematics of Computation.&lt;/em>&lt;/li>
&lt;li>&lt;em>Sinclair, J. (2011). Deterministic Miller-Rabin primality testing.&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>【数学解説】RSA暗号の仕組みを高校生でもわかるように説明する</title><link>http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/</link><pubDate>Fri, 11 Sep 2026 13:00:00 +0900</pubDate><guid>http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/</guid><description>&lt;img src="http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/img/eyecatch.jpg" alt="Featured image of post 【数学解説】RSA暗号の仕組みを高校生でもわかるように説明する" />&lt;p>インターネット社会の安全を根底から支えている技術の一つが「RSA暗号」です。オンラインショッピングでのクレジットカード決済、友人とのSNSのやり取り、会社の機密情報の送受信など、私たちが毎日何気なく使っている通信の多くは、このRSA暗号やその後継技術によって守られています。&lt;/p>
&lt;p>しかし、「暗号」と聞くと、スパイ映画に出てくるような複雑な暗号機や、一部の天才しか理解できない超高度な数学を想像するかもしれません。確かに現代の暗号理論は高度な数学に基づいていますが、&lt;strong>RSA暗号の根本的な仕組みは、高校で習う数学（整数の性質、素数、合同式など）の知識があれば十分に理解できる&lt;/strong>ものです。&lt;/p>
&lt;p>この記事では、高校数学の知識を出発点として、RSA暗号がどのような数学的原理で動いているのか、なぜ解読が難しいのかを、ステップバイステップで徹底的に解説します。数学が少し苦手な方でも理解できるように、具体例を交えながら丁寧に説明していきます。&lt;/p>
&lt;hr>
&lt;h2 id="1-共通鍵暗号と公開鍵暗号">1. 共通鍵暗号と公開鍵暗号
&lt;/h2>&lt;p>RSA暗号の数学的な仕組みに入る前に、まず暗号の基本的な考え方について整理しておきましょう。暗号方式は大きく分けて「共通鍵暗号」と「公開鍵暗号」の2種類があります。&lt;/p>
&lt;h3 id="11-共通鍵暗号方式の限界">1.1 共通鍵暗号方式の限界
&lt;/h3>&lt;p>昔から使われている暗号の多くは「共通鍵暗号方式」と呼ばれるものです。これは、&lt;strong>「暗号化（メッセージを秘密の暗号文に変換すること）」と「復号（暗号文を元のメッセージに戻すこと）」に同じ鍵を使う&lt;/strong>方式です。&lt;/p>
&lt;p>例えば、アリスがボブに秘密の手紙を送るとします。アリスは南京錠（共通鍵）を使って箱に手紙を入れて鍵をかけます。ボブがその箱を開けるためには、アリスが使ったのと同じ鍵を持っている必要があります。&lt;/p>
&lt;p>この方式には大きな問題があります。「鍵の配送問題」です。遠く離れたアリスとボブが初めて通信する場合、どのようにして盗聴されることなく鍵を共有すればよいのでしょうか？もし鍵を郵送している途中で第三者に盗まれたら、その後の暗号通信はすべて筒抜けになってしまいます。&lt;/p>
&lt;h3 id="12-画期的な発明公開鍵暗号方式">1.2 画期的な発明「公開鍵暗号方式」
&lt;/h3>&lt;p>この鍵の配送問題を解決するために考案されたのが「公開鍵暗号方式」です。RSA暗号もこの一種です。&lt;/p>
&lt;p>公開鍵暗号方式では、&lt;strong>「暗号化するための鍵（公開鍵）」と「復号するための鍵（秘密鍵）」という2つの異なる鍵&lt;/strong>を使います。&lt;/p>
&lt;ol>
&lt;li>受信者のボブは、「公開鍵」と「秘密鍵」のペアを作成します。&lt;/li>
&lt;li>ボブは「公開鍵」を世界中に公開します（誰が手に入れても構いません）。&lt;/li>
&lt;li>送信者のアリスは、ボブの「公開鍵」を使ってメッセージを暗号化し、送信します。&lt;/li>
&lt;li>暗号化されたメッセージは、ボブだけが持っている「秘密鍵」でしか復号できません。&lt;/li>
&lt;/ol>
&lt;p>これを南京錠で例えると、ボブは「開いた状態の南京錠（公開鍵）」をたくさん作って世界中にばらまきます。アリスはボブ宛のメッセージを箱に入れ、拾ったボブの南京錠でカチャッと鍵をかけます。南京錠は一度閉まると、ボブが持っている「マスターキー（秘密鍵）」でしか開けられません。途中で誰かが箱を盗んでも、マスターキーがないので開けられないのです。&lt;/p>
&lt;div class="mermaid">graph TD
A["アリス (送信者)"] --> B["平文 (メッセージ)"]
B --> C["暗号化処理"]
D["ボブの公開鍵 (誰でも入手可能)"] --> C
C --> E["インターネット経由で送信: 暗号文"]
E --> F["復号処理"]
G["ボブの秘密鍵 (ボブだけが持つ)"] --> F
F --> H["復元された平文 (メッセージ)"]
H --> I["ボブ (受信者)"]&lt;/div>
&lt;p>この画期的なシステムを実現するためには、「公開鍵で簡単に暗号化できるが、秘密鍵がないと絶対に復号できない」という、ある種の**「一方向性関数（一方通行の数学的パズル）」**が必要になります。そのパズルの部品として目をつけられたのが、私たちがよく知る「素数」でした。&lt;/p>
&lt;hr>
&lt;h2 id="2-rsa暗号を支える数学的基礎1素数と素因数分解">2. RSA暗号を支える数学的基礎1：素数と素因数分解
&lt;/h2>&lt;p>RSA暗号の安全性は、**「巨大な数の素因数分解は非常に難しい」**という数学的事実に基づいています。&lt;/p>
&lt;h3 id="21-素数とは">2.1 素数とは
&lt;/h3>&lt;p>素数とは、「1と自分自身でしか割り切れない、1より大きい自然数」のことです。
例：$2, 3, 5, 7, 11, 13, 17, 19, 23...$&lt;/p>
&lt;p>素数は、すべての整数の「原子」のようなものです。どんな自然数も、素数の掛け算の形に分解することができます。これを&lt;strong>素因数分解&lt;/strong>と呼びます。例えば、$60 = 2^2 \times 3 \times 5$ のように、順番を無視すればただ一通りに素因数分解できることは「算術の基本定理」として知られています。&lt;/p>
&lt;h3 id="22-素因数分解の難しさ一方向性関数">2.2 素因数分解の難しさ（一方向性関数）
&lt;/h3>&lt;p>ここで重要なのは、**「掛け算は簡単だが、素因数分解は難しい」**という非対称性です。&lt;/p>
&lt;p>例えば、次の2つの素数の掛け算を暗算で計算してみてください。
$11 \times 13 = ?$
これは簡単ですね。答えは $143$ です。&lt;/p>
&lt;p>では、次の数はどうでしょう？
$323$ を素因数分解してください。
どうでしょうか？少し時間がかかるはずです。（答えは $17 \times 19$ です）。&lt;/p>
&lt;p>数が小さければ人間でも何とか計算できますが、数が大きくなるとコンピュータを使っても計算が爆発的に難しくなります。現在主流のRSA暗号では、2048ビット（10進数で約600桁）という途方もなく巨大な素数 $p$ と $q$ を掛け合わせた数 $N = p \times q$ を使います。&lt;/p>
&lt;p>2つの巨大な素数 $p$ と $q$ が与えられたとき、$N$ を計算するのはコンピュータにとって一瞬（ミリ秒以下）です。しかし、逆に $N$ だけを与えられて元の $p$ と $q$ を見つけ出すことは、現在の最速のスーパーコンピュータを何兆年回しても解けないほど時間がかかります。&lt;/p>
&lt;p>この**「計算の非対称性（片道は簡単、逆道は困難）」**が、公開鍵と秘密鍵の関係性を作り出す土台になります。&lt;/p>
&lt;hr>
&lt;h2 id="3-rsa暗号を支える数学的基礎2合同式モジュロ演算">3. RSA暗号を支える数学的基礎2：合同式（モジュロ演算）
&lt;/h2>&lt;p>RSA暗号の計算は、私たちが普段使っているような無限に数が大きくなる足し算や掛け算ではなく、ある数で割った「余り」の世界で行われます。これを**合同式（モジュロ演算）**と呼びます。&lt;/p>
&lt;h3 id="31-時計の数学">3.1 時計の数学
&lt;/h3>&lt;p>モジュロ演算はよく「時計の数学」に例えられます。現在10時だとして、そこから5時間後は何時でしょうか？ $10 + 5 = 15$ 時ですが、通常の12時間時計では「3時」と答えます。これは、15を12で割った余りが3だからです。&lt;/p>
&lt;p>数学の世界では、これを次のように書き表します。
&lt;/p>
$$ 15 \equiv 3 \pmod{12} $$
&lt;p>
「15と3は、12を法として合同である（12で割った余りが等しい）」と読みます。&lt;/p>
&lt;h3 id="32-合同式の基本的な性質">3.2 合同式の基本的な性質
&lt;/h3>&lt;p>合同式には、等式（$=$）とよく似た非常に便利な性質があります。法（割る数）を $N$ とします。
$a \equiv b \pmod N$ かつ $c \equiv d \pmod N$ のとき、以下が成り立ちます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>足し算:&lt;/strong> $a + c \equiv b + d \pmod N$&lt;/li>
&lt;li>&lt;strong>引き算:&lt;/strong> $a - c \equiv b - d \pmod N$&lt;/li>
&lt;li>&lt;strong>掛け算:&lt;/strong> $a \times c \equiv b \times d \pmod N$&lt;/li>
&lt;li>&lt;strong>べき乗:&lt;/strong> $a^k \equiv b^k \pmod N$ （$k$ は自然数）&lt;/li>
&lt;/ol>
&lt;p>特に重要なのは「べき乗」の性質です。これは**「余りの累乗は、累乗の余りに等しい」**ことを意味します。
例えば、$7^{100}$ を $5$ で割った余りを求めたいとします。真面目に $7$ を100回掛けてから $5$ で割るのは大変ですが、合同式の性質を使えば、$7 \equiv 2 \pmod 5$ なので、$7^{100} \equiv 2^{100} \pmod 5$ となり、計算を飛躍的に簡単にしていくことができます。暗号の世界では非常に大きな数の累乗を扱うため、この性質が不可欠です。&lt;/p>
&lt;hr>
&lt;h2 id="4-rsa暗号を支える数学的基礎3オイラー関数とオイラーの定理">4. RSA暗号を支える数学的基礎3：オイラー関数とオイラーの定理
&lt;/h2>&lt;p>ここからがRSA暗号の核心となる魔法の数学です。「フェルマーの小定理」の一般化である「オイラーの定理」が登場します。&lt;/p>
&lt;h3 id="41-オイラーのトーティエント関数-phin">4.1 オイラーのトーティエント関数 $\phi(N)$
&lt;/h3>&lt;p>オイラーのトーティエント関数（$\phi$ 関数）は、ある自然数 $N$ に対して、**「1から $N$ までの自然数のうち、$N$ と互いに素（最大公約数が1）である数の個数」**を返す関数です。&lt;/p>
&lt;p>いくつか例を見てみましょう。&lt;/p>
&lt;ul>
&lt;li>$\phi(5)$: 1, 2, 3, 4, 5 の中で 5 と互いに素なのは 1, 2, 3, 4 の4個。よって $\phi(5) = 4$。&lt;/li>
&lt;li>$\phi(6)$: 1, 2, 3, 4, 5, 6 の中で 6 と互いに素なのは 1, 5 の2個。よって $\phi(6) = 2$。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【素数の場合の特別な性質】&lt;/strong>
$p$ が素数の場合、1から $p-1$ までのすべての数が $p$ と互いに素になります。したがって、
&lt;/p>
$$ \phi(p) = p - 1 $$
&lt;p>
となります。&lt;/p>
&lt;p>&lt;strong>【素数の積の場合の特別な性質】&lt;/strong>
2つの異なる素数 $p$ と $q$ について、$N = p \times q$ とした場合、$\phi(N)$ は以下の計算で簡単に求めることができます。
&lt;/p>
$$ \phi(N) = \phi(p) \times \phi(q) = (p - 1)(q - 1) $$
&lt;p>
この性質が、RSA暗号の「秘密の裏口（トラップドア）」として機能します。$p$ と $q$ を知っている人（鍵の作成者）は $\phi(N)$ を一瞬で計算できますが、$N$ しか知らない第三者は、$N$ を素因数分解しない限り $\phi(N)$ を求めることができないのです。&lt;/p>
&lt;h3 id="42-オイラーの定理">4.2 オイラーの定理
&lt;/h3>&lt;p>レオンハルト・オイラーは、この $\phi(N)$ を使って次のような美しい定理を証明しました。&lt;/p>
&lt;p>&lt;strong>オイラーの定理：&lt;/strong>
整数 $a$ と $N$ が互いに素であるとき、以下の合同式が成り立つ。
&lt;/p>
$$ a^{\phi(N)} \equiv 1 \pmod N $$
&lt;p>これは、「ある数 $a$ を $\phi(N)$ 回掛け合わせて $N$ で割ると、余りが必ず $1$ になる」という驚くべき性質です。（$N$ が素数 $p$ の場合は $a^{p-1} \equiv 1 \pmod p$ となり、フェルマーの小定理と呼ばれます）。&lt;/p>
&lt;p>このオイラーの定理を変形してみましょう。両辺に $a$ をもう一回掛けます。
&lt;/p>
$$ a^{\phi(N) + 1} \equiv a \pmod N $$
&lt;p>さらに、任意の整数 $k$ に対して、$a^{k \cdot \phi(N)}$ も $1^k = 1$ になるため、次の式が成り立ちます。
&lt;/p>
$$ a^{k \cdot \phi(N) + 1} \equiv a \pmod N $$
&lt;p>この式こそが、RSA暗号の**「暗号化して復号すると元に戻る」**という魔法を成り立たせている根本原理なのです。&lt;/p>
&lt;hr>
&lt;h2 id="5-rsa暗号のアルゴリズム鍵生成暗号化復号のステップ">5. RSA暗号のアルゴリズム：鍵生成・暗号化・復号のステップ
&lt;/h2>&lt;p>基礎知識が揃ったところで、いよいよRSA暗号の具体的な手順を見ていきましょう。RSA暗号は大きく「1. 鍵の生成」「2. 暗号化」「3. 復号」の3つのフェーズに分かれます。&lt;/p>
&lt;div class="mermaid">flowchart TD
A1["1. 素数 p, q を選ぶ"] --> A2["N = p × q を計算"]
A1 --> A3["φ(N) = (p-1)(q-1) を計算"]
A3 --> A4["φ(N)と互いに素な e を選ぶ"]
A3 --> A5["e × d ≡ 1 (mod φ(N)) となる d を計算"]
A2 --> A6["公開鍵 (N, e)"]
A4 --> A6
A5 --> A7["秘密鍵 d"]
B1["2. 平文メッセージ M"] --> B2["C ≡ M^e (mod N) を計算"]
A6 -.-> B2
B2 --> B3["暗号文 C を送信"]
B3 --> C1["3. 受信した暗号文 C"]
C1 --> C2["M ≡ C^d (mod N) を計算"]
A7 -.-> C2
C2 --> C3["元の平文メッセージ M を取得"]&lt;/div>
&lt;h3 id="51-鍵生成key-generation">5.1 鍵生成（Key Generation）
&lt;/h3>&lt;p>受信者であるボブは、自分のための「公開鍵」と「秘密鍵」を生成します。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>素数の選択:&lt;/strong> 2つの大きな素数 $p$ と $q$ をランダムに選びます。&lt;/li>
&lt;li>&lt;strong>法 $N$ の計算:&lt;/strong> $N = p \times q$ を計算します。この $N$ は公開されます。&lt;/li>
&lt;li>&lt;strong>$\phi(N)$ の計算:&lt;/strong> オイラー関数 $\phi(N) = (p - 1)(q - 1)$ を計算します。これはボブだけの秘密の数です。&lt;/li>
&lt;li>&lt;strong>公開鍵 $e$ の選択:&lt;/strong> $1 &lt; e &lt; \phi(N)$ であり、かつ $\phi(N)$ と互いに素である整数 $e$ を選びます。&lt;/li>
&lt;li>&lt;strong>秘密鍵 $d$ の計算:&lt;/strong> 次の条件を満たす整数 $d$ を見つけます。
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
これはつまり、「$e \times d$ を $\phi(N)$ で割った余りが $1$ になるような数 $d$」です。&lt;/li>
&lt;/ol>
&lt;p>これで鍵の準備は完了です。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>公開鍵:&lt;/strong> $(N, e)$ のペア。全世界に公開します。&lt;/li>
&lt;li>&lt;strong>秘密鍵:&lt;/strong> $d$。絶対に誰にも教えません。&lt;/li>
&lt;/ul>
&lt;h3 id="52-暗号化encryption">5.2 暗号化（Encryption）
&lt;/h3>&lt;p>アリスはボブに秘密のメッセージ $M$ を送りたいとします。（$M$ は文字を数値化したもので、$0 \le M &lt; N$ とします）。アリスはボブの公開鍵 $(N, e)$ を使って次のように計算します。&lt;/p>
$$ C \equiv M^e \pmod N $$
&lt;p>「メッセージ $M$ を $e$ 乗して、$N$ で割った余り $C$」を計算します。この $C$ が暗号文です。&lt;/p>
&lt;h3 id="53-復号decryption">5.3 復号（Decryption）
&lt;/h3>&lt;p>ボブは暗号文 $C$ を受け取ります。ボブは秘密鍵 $d$ を使って次のように計算します。&lt;/p>
$$ M \equiv C^d \pmod N $$
&lt;p>「暗号文 $C$ を $d$ 乗して、$N$ で割った余り」を計算すると、なんと元のメッセージ $M$ が復元されるのです！&lt;/p>
&lt;hr>
&lt;h2 id="6-なぜ復号で元に戻るのか数学的証明">6. なぜ復号で元に戻るのか？（数学的証明）
&lt;/h2>&lt;p>「$C$ を $d$ 乗するだけで、どうして元の $M$ に戻るの？」と疑問に思うかもしれません。ここで、先ほどの「オイラーの定理」が威力を発揮します。&lt;/p>
&lt;p>復号の計算式 $C^d \pmod N$ に、暗号化の式 $C = M^e$ を代入してみましょう。
&lt;/p>
$$ C^d \equiv (M^e)^d \equiv M^{ed} \pmod N $$
&lt;p>ここで、鍵生成のステップ 5 を思い出してください。ボブは $d$ を作るときに、$e \times d \equiv 1 \pmod{\phi(N)}$ となるように選びました。これは「$ed$ は $\phi(N)$ の倍数に $1$ を足した数である」ということを意味します。整数 $k$ を使って次のように書けます。
&lt;/p>
$$ ed = k \cdot \phi(N) + 1 $$
&lt;p>これを指数部分に代入し、指数法則を使って分解します。
&lt;/p>
$$ M^{ed} = M^{k \cdot \phi(N) + 1} = M^{k \cdot \phi(N)} \times M^1 = (M^{\phi(N)})^k \times M $$
&lt;p>ここで、メッセージ $M$ と $N$ が互いに素であると仮定すると、&lt;strong>オイラーの定理&lt;/strong>より $M^{\phi(N)} \equiv 1 \pmod N$ となります。
&lt;/p>
$$ (M^{\phi(N)})^k \times M \equiv 1^k \times M \equiv M \pmod N $$
&lt;p>したがって、見事に以下の式が成り立ちます。
&lt;/p>
$$ C^d \equiv M \pmod N $$
&lt;p>アリスは $d$ を知らず、盗聴者も $d$ を知らないので、$C$ から $M$ を取り出せるのは $d$ を持っているボブだけなのです。&lt;/p>
&lt;hr>
&lt;h2 id="7-具体例小さな素数を使って手計算でrsaを体験してみよう">7. 具体例：小さな素数を使って手計算でRSAを体験してみよう
&lt;/h2>&lt;p>実際に小さな数字（素数）を使って、アリスからボブへ暗号通信をしてみましょう。&lt;/p>
&lt;p>&lt;strong>【ボブの鍵生成フェーズ】&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>2つの素数 $p=11$, $q=13$ を選びます。&lt;/li>
&lt;li>$N = 11 \times 13 = 143$ を計算します。&lt;/li>
&lt;li>$\phi(N) = (11 - 1) \times (13 - 1) = 10 \times 12 = 120$ を計算します。&lt;/li>
&lt;li>$\phi(N)=120$ と互いに素な公開鍵 $e$ を選びます。ここでは $e=7$ にします。&lt;/li>
&lt;li>秘密鍵 $d$ を求めます。$7 \times d \equiv 1 \pmod{120}$ となる $d$ を探します。
方程式 $7d = 120k + 1$ において、$k=6$ のとき $721$ となり、$721 \div 7 = 103$。
したがって、$d = 103$ となります。&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>公開鍵：$(N=143, e=7)$&lt;/li>
&lt;li>秘密鍵：$d=103$&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【アリスの暗号化フェーズ】&lt;/strong>
メッセージ $M = 9$ を送りたいとします。
式：$C \equiv 9^7 \pmod{143}$
$9^7 = 4,782,969$。これを 143 で割ると $33447$ 余り $48$。
暗号文 $C = 48$ となりました。&lt;/p>
&lt;p>&lt;strong>【ボブの復号フェーズ】&lt;/strong>
ボブは暗号文 $C = 48$ を受け取り、秘密鍵 $d = 103$ を使って復号します。
式：$M \equiv 48^{103} \pmod{143}$
計算機で &lt;code>(48 ** 103) % 143&lt;/code> を実行すると、見事に結果は「&lt;strong>9&lt;/strong>」となります！元のメッセージを無事に受け取ることができました。&lt;/p>
&lt;hr>
&lt;h2 id="8-秘密鍵-d-の求め方拡張ユークリッドの互除法">8. 秘密鍵 $d$ の求め方：拡張ユークリッドの互除法
&lt;/h2>&lt;p>手計算の例では勘で $k$ を探して $d=103$ を見つけましたが、数が何百桁にもなるとこの方法は不可能です。実際のプログラムでは**「拡張ユークリッドの互除法」**というアルゴリズムを使います。&lt;/p>
&lt;p>$7d \equiv 1 \pmod{120}$ を解くということは、$7d + 120y = 1$ を満たす整数 $d, y$ を見つけることと同じです。ユークリッドの互除法を逆算していくことで、これを機械的に求めることができます。&lt;/p>
&lt;ol>
&lt;li>$120 \div 7 = 17$ 余り $1$&lt;/li>
&lt;li>これを変形すると、$1 = 120 - 17 \times 7$&lt;/li>
&lt;li>つまり、$-17 \times 7 \equiv 1 \pmod{120}$&lt;/li>
&lt;/ol>
&lt;p>$-17$ は、法 $120$ の世界では $120 - 17 = 103$ と同じ意味になります。したがって、$d = 103$ が一瞬で求まるのです。この方法はどんなに巨大な数でも非常に高速に計算できます。&lt;/p>
&lt;hr>
&lt;h2 id="9-rsa暗号のもう一つの顔デジタル署名">9. RSA暗号のもう一つの顔：デジタル署名
&lt;/h2>&lt;p>RSA暗号の素晴らしい点は、公開鍵と秘密鍵の役割を逆にすることで**「デジタル署名」**としても使えることです。&lt;/p>
&lt;p>暗号化のときは「公開鍵で暗号化 $\Rightarrow$ 秘密鍵で復号」でしたが、
デジタル署名では「秘密鍵で暗号化 $\Rightarrow$ 公開鍵で復号」という手順を踏みます。&lt;/p>
&lt;div class="mermaid">flowchart TD
A1["1. アリスが秘密鍵で署名作成"] --> A2["S ≡ M^d (mod N)"]
A2 --> A3["メッセージ M と署名 S を送信"]
A3 --> B1["2. ボブが公開鍵で署名検証"]
B1 --> B2["M' ≡ S^e (mod N) を計算"]
B2 --> B3["M' と M が一致するか確認"]&lt;/div>
&lt;p>アリスが自身の秘密鍵 $d$ を使ってメッセージを変換し（これが署名 $S$）、ボブに送ります。ボブはアリスの公開鍵 $e$ を使って検証計算をします。もし計算結果が元のメッセージと一致すれば、「アリスの秘密鍵でしか作れないデータである」ことと、「メッセージが途中で改ざんされていないこと」が同時に証明されるのです。&lt;/p>
&lt;hr>
&lt;h2 id="10-プログラムで実感するrsa暗号">10. プログラムで実感するRSA暗号
&lt;/h2>&lt;p>手計算では大変な累乗計算も、Pythonを使えば非常に簡単に実装できます。以下はRSA暗号のコアとなるロジックを体験できるPythonコードです。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;最大公約数を求める&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;秘密鍵 d を求める（Python 3.8以降の組み込み機能を利用）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 鍵生成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">11&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">N&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">q&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">phi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;公開鍵: (N=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">N&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, e=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">), 秘密鍵: d=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 暗号化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ciphertext&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;暗号文: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ciphertext&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 復号&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">decrypted_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ciphertext&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;復号されたメッセージ: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decrypted_message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Pythonの &lt;code>pow(base, exp, mod)&lt;/code> 関数は内部で「繰り返し二乗法」という高速アルゴリズムを使っているため、何百桁の数であっても一瞬で計算が終わります。&lt;/p>
&lt;hr>
&lt;h2 id="11-まとめと未来の暗号技術">11. まとめと未来の暗号技術
&lt;/h2>&lt;p>高校数学の知識をベースに、RSA暗号の仕組みを解き明かしてきました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>素因数分解の困難性:&lt;/strong> $p \times q = N$ は簡単だが、$N$ から $p, q$ を見つけるのは非常に難しい。&lt;/li>
&lt;li>&lt;strong>合同式とオイラーの定理:&lt;/strong> $a^{\phi(N)} \equiv 1 \pmod N$ という法則により、「ある数で累乗すると元に戻る」魔法のトラップドアが完成する。&lt;/li>
&lt;li>&lt;strong>公開鍵と秘密鍵:&lt;/strong> 誰でも暗号化できるが、復号できるのは正当な受信者だけ。&lt;/li>
&lt;/ol>
&lt;p>現在使われているRSA暗号の $N$ は600桁以上あり、世界中のスーパーコンピュータを総動員しても素因数分解には宇宙の年齢以上の時間がかかります。しかし、近年研究が進んでいる「量子コンピュータ」が将来実用化されると、「ショアのアルゴリズム」によってこの素因数分解が一瞬で解かれてしまう可能性があります。そのため、現在は量子コンピュータでも解読できない「耐量子計算機暗号」の開発が世界中で急ピッチで進められています。&lt;/p>
&lt;p>「役に立たない」と思われがちな高度な数学が、実は私たちの日常生活を根底から守っている。RSA暗号は、そんな数学の奥深さと美しさを教えてくれる最高の教材です。この記事を通して、暗号と数学の面白さを少しでも感じていただけたなら幸いです。&lt;/p></description></item></channel></rss>