C++でmod pの平方根を求める方法(Tonelli–Shanksアルゴリズム)
問題の概要
この問題では、整数 n と素数 p が与えられ、「n の mod p における平方根(モジュラ平方根)」を求めることを目標とします。
具体例を見てみましょう。
入力 : n = 4, p = 11 出力 : 9
これは、92 = 81 ≡ 4 (mod 11) が成立するためです。つまり 9 は 4 のモジュラ平方根となっています。
解法のアプローチ
本記事では Tonelli–Shanks(トネリ・シャンクス)アルゴリズムを使用します。
Tonelli–Shanksアルゴリズムとは、モジュラ演算において x2 ≡ n (mod p) という形の合同式を満たす x を求めるための古典的かつ強力なアルゴリズムです。
アルゴリズムの手順
ステップ1 : n^((p−1)/2) mod p の値を計算します。結果が p − 1 と等しい場合、モジュラ平方根は存在しません(オイラーの基準による判定)。
ステップ2 : p − 1 を s × 2e の形に分解します。ここで s は正の奇数、e は正の整数です。
ステップ3 : q^((p−1)/2) ≡ −1 (mod p) を満たすような非平方数 q を見つけます。
ステップ4 : ループ処理によって m を探索し、条件に応じて x などの変数を更新していきます。
まず、b^(2^m) ≡ 1 (mod p) となる m(0 ≤ m ≤ r−1)を探します。
m が 0 であれば x を答えとして返します。そうでない場合は、次のように各値を更新します。
x = x * g^(2^(r − m − 1)) b = b * g^(2^(r − m)) g = g^(2^(r − m − 1)) r = m
C++での実装例
上記の解法の動作を示すプログラムがこちらです。
#include <iostream>
#include <math.h>
using namespace std;
int powerMod(int base, int exponent, int modulus) {
int result = 1;
base = base % modulus;
while (exponent > 0) {
if (exponent % 2 == 1)
result = (result * base)% modulus;
exponent = exponent >> 1;
base = (base * base) % modulus;
}
return result;
}
int gcd(int a, int b) {
if (b == 0)
return a;
else
return gcd(b, a % b);
}
int orderValues(int p, int b) {
if (gcd(p, b) != 1) {
return -1;
}
int k = 3;
while (1) {
if (powerMod(b, k, p) == 1)
return k;
k++;
}
}
int findx2e(int x, int& e) {
e = 0;
while (x % 2 == 0) {
x /= 2;
e++;
}
return x;
}
int calcSquareRoot(int n, int p) {
if (gcd(n, p) != 1) {
return -1;
}
if (powerMod(n, (p - 1) / 2, p) == (p - 1)) {
return -1;
}
int s, e;
s = findx2e(p - 1, e);
int q;
for (q = 2; ; q++) {
if (powerMod(q, (p - 1) / 2, p) == (p - 1))
break;
}
int x = powerMod(n, (s + 1) / 2, p);
int b = powerMod(n, s, p);
int g = powerMod(q, s, p);
int r = e;
while (1) {
int m;
for (m = 0; m < r; m++) {
if (orderValues(p, b) == -1)
return -1;
if (orderValues(p, b) == pow(2, m))
break;
}
if (m == 0)
return x;
x = (x * powerMod(g, pow(2, r - m - 1), p)) % p;
g = powerMod(g, pow(2, r - m), p);
b = (b * g) % p;
if (b == 1)
return x;
r = m;
}
}
int main() {
int n = 3;
int p = 13;
int sqrtVal = calcSquareRoot(n, p);
if (sqrtVal == -1)
cout<<"Modular square root is not exist";
else
cout<<"Modular square root of the number is "<<sqrtVal;
}
コードのポイント
- powerMod(): 繰り返し二乗法を用いて、大きな指数でも高速にべき剰余を計算する補助関数です。
- gcd(): 最大公約数を求めます。n と p が互いに素でない場合は解を返しません。
- orderValues(): mod p における b の位数(何乗すると 1 になるか)を求めます。
- findx2e(): 与えられた整数を奇数部分 s と 2 のべき乗部分 e に分解します。
- calcSquareRoot(): Tonelli–Shanksアルゴリズムの本体であり、これらの関数を組み合わせてモジュラ平方根を計算します。
出力
Modular square root of the number is 9
この実行結果から、92 ≡ 3 (mod 13) が成立していること、すなわち 9 が 3 のモジュラ平方根であることが確認できます。
-
C++で汚染された二分木を復元して要素を検索する方法
問題の概要次のようなルールに従う二分木を考えます。root.val == 0 であるtreeNode.val が x であり、treeNode.left が NULL でない場合、treeNode.left.val = 2 * x + 1 となるtreeNode.val が x であり、treeNode.right が NULL でない場合、treeNode.right.val = 2 * x + 2 となるここで、この二分木は「汚染」されているものとします。つまり、すべてのノードの値が -1 に書き換えられている状態です。まず二分木を復元した上で、以下の FindElements クラスを実
-
【C++】順序統計アルゴリズムでリストからi番目に大きい数を求める方法
この記事では、順序統計アルゴリズム(Order-Statistic Algorithm)を用いて、指定されたリスト(配列)の中から i 番目に大きい数を求めるC++プログラムを紹介します。この手法は、二分探索木(BST)にデータを挿入し、各ノードにランク(順位)を割り当てることで、任意の順位の要素を効率的に取り出せる点が特徴です。アルゴリズムの概要全体の流れは「挿入 → ランク割り当て → 選択」の3つのステップで構成されています。それぞれの関数の動作を詳しく見ていきましょう。1. Insert():木へのノード挿入引数として根(root)と挿入する値 d を受け取ります。木が完全に空の場合は