C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で素数を法とする原始根を求める方法

問題概要

この問題では、素数 N が与えられ、素数を法とする原始根(Primitive Root)を求めることが課題となります。

原始根とは — N より小さい数 r のうち、[0, n-2] の範囲のすべての x に対して rx (mod N) の値が互いに異なるような数のことを指します。

例を見て理解しましょう:

入力 : N = 5
出力 : 2

なお、数学的には素数 N を法とする原始根の総個数は φ(N−1)(オイラー関数の値)であることが知られています。本記事では、その中でも最小の原始根を効率よく求める手法を紹介します。

解法アプローチ

1. 素朴な解法(全探索)

最も単純な解法は試行錯誤(全探索)によるものです。2 から (N−1) までのすべての数について、x を [0, n-2] の範囲で動かしながら条件を満たすかを順に確認し、条件を満たす値が見つかった時点で探索を終了します。

この方法は実装が簡単ですが、時間計算量が O(N2) となるため、N が大きい場合は実行に非常に長い時間がかかる可能性があります。

2. オイラーのトーティエント関数を用いた効率的な解法

より効率的なアプローチとして、オイラーのトーティエント関数 φ(N) を活用します。

数 r が N の原始根であるためには、法 N における r の乗法位数が φ(N) と一致する必要があります。具体的な手順は以下の通りです。

  • まず、素数 N に対して (N−1) のすべての素因数を求めます。
  • 次に、(N−1) を各素因数で割った値を指数として冪乗を計算します。
  • その冪乗を mod n で評価し、結果が一度も 1 にならないならば、r は原始根です。

原始根は複数存在しうるため、最初に見つかった値(すなわち最小の原始根)を返却すればよいことになります。

サンプルコード(C++)

#include<bits/stdc++.h>
using namespace std;
int calcPowerMod(int x, unsigned int y, int p){
    int modVal = 1;
    x = x % p;
    while (y > 0){
        if (y & 1)
            modVal = (modVal*x) % p;
        y = y >> 1;
        x = (x*x) % p;
    }
    return modVal;
}
void findAllPrimeFactors(unordered_set<int> &s, int n){
    while (n%2 == 0){
        s.insert(2);
        n = n/2;
    }
    for (int i = 3; i*i <= n; i = i+2){
        while (n%i == 0){
            s.insert(i);
            n = n/i;
        }
    }
    if (n > 2)
        s.insert(n);
}
int findSmallestPrimitiveRoot(int n){
    unordered_set<int> primes;
    int phi = n-1;
    findAllPrimeFactors(primes, phi);
    for (int r=2; r<=phi; r++){
        bool flag = false;
        for (auto it = primes.begin(); it != primes.end(); it++){
            if (calcPowerMod(r, phi/(*it), n) == 1){
                flag = true;
                break;
            }
        }
        if (flag == false)
            return r;
    }
    return -1;
}
int main(){
    int n = 809;
    cout<<"The smallest primitive root is "<<findSmallestPrimitiveRoot(n);
    return 0;
}

出力結果

The smallest primitive root is 3

このように、素因数分解と繰り返し二乗法(べき乗の剰余計算)を組み合わせることで、大きな素数に対しても高速に最小の原始根を求めることができます。

  1. C++で文字列の部分文字列の総数を求める方法を解説

    この記事では、与えられた文字列から作成できる空でない部分文字列の個数を求める方法について解説します。入力 : string = "moon" 出力 : 10 説明 : 部分文字列は m、o、o、n、mo、oo、on、moo、oon、moon の 10 個です。 入力 : string = "yellow" 出力 : 21解法のアプローチ文字列の長さを n とします。上の例からも分かるように、考えられるすべての部分文字列の個数を求めるには、長さ n、(n-1)、(n-2)、(n-3)、……2、1 の部分文字列の個数を順に加算していく必要があります。部分文

  2. C++で列車の停車駅の組み合わせ数を求める方法

    地点XとYの間にはn個の中間駅があるとします。ここで、「どの2つの停車駅も隣り合わない」という条件のもとで、s個の駅に停車する列車の配置方法が何通りあるかを求める問題を考えてみましょう。この記事では、停車駅の組み合わせ数を求めるためのアプローチを段階的に詳しく解説します。この問題は、本質的には組合せ論の問題であり、s個の停車駅の選び方の総数を求めることになります。 問題を解くアプローチ まず具体例として、中間駅が8個あり、そのうち3個の駅に停車させたい場合を考えてみます。 n = 8, s = 3 このとき、列車が停車できない駅は(n − s)、つまり5個残ることになります。 停車できない