N の約数を全列挙するアルゴリズム
整数 N の約数とは「整数 N を割り切ることができる整数またはその集合」のことです。
単純なアルゴリズムでは約数の全列挙に \(O(N)\) だけかかりますが、約数の性質を活かすと \(O(\sqrt{N})\) で全列挙することが可能です。
アルゴリズム
単純なアルゴリズム:
- 1 から N まで以下を繰り返す
- N を割り切れたら約数として保存する
※ 1 から N まで全探索して、全ての約数を列挙します
効率的なアルゴリズム:
- d=1 から \(\lfloor \sqrt{N} \rfloor\) まで以下を繰り返す
- N を割り切れたら、d と \(\frac{N}{d}\) を約数として保存する
※ d が N の約数と分かった場合、\(\frac{N}{d}\) もN の約数だと分かります。この性質を利用すると 1 から \(\sqrt{N}\) までを探索すれば良いです。
計算量
- 単純なアルゴリズム:\(O(N)\)
- 効率的なアルゴリズム:\(O(\sqrt{N})\)
単純なアルゴリズムでは、1 から N まで全ての数を確かめるので、計算量は \(O(N)\) になります。
効率的なアルゴリズムでは、1 から \(\sqrt{N}\) まで全ての数を確かめるので、計算量は \(O(\sqrt{N})\) になります。
C++ での実装例
単純なアルゴリズム
/* divisor(n) 入力:整数 n 出力:nのすべての約数 計算量:O(N) */ vector<long long> divisor(long long n) { vector<long long> ret; for (long long i = 1; i <= n; i++) { if (n % i == 0) { ret.push_back(n / i); } } return ret; }
効率的なアルゴリズム
d=\(\sqrt{N}\) が整数の場合は、\(\frac{N}{d} = \frac{N}{\sqrt{N}}\) も同じく \(\sqrt{N}\) になるので重複して数えないようにする必要があります。
また、約数を昇順に並べて出力したい場合は、以下のように結果をソートする必要があるでしょう。(ソートを行うと \(O(\sqrt{N})\) を少し超えてしまいます。\(\frac{N}{d}\) の部分は別の配列に保存しておき、最後に並び順を反転してからつなげると \(O(\sqrt{N})\) で昇順に並べることが可能です。)
/* divisor(n) 入力:整数 n 出力:nのすべての約数 計算量:O(√n) */ vector<long long> divisor(long long n) { vector<long long> ret; for (long long i = 1; i * i <= n; i++) { if (n % i == 0) { ret.push_back(i); if (i * i != n) ret.push_back(n / i); } } sort(ret.begin(), ret.end()); // 昇順に並べる return ret; }
練習問題
- AOJ ITP1_3_D How Many Divisors?:c の約数を全列挙しましょう
- [AtCoder] ARC026 B – 完全数:\(O(N)\) のアルゴリズムでは50点しか取れません
ディスカッション
コメント一覧
まだ、コメントがありません