C++で解くn番目のスーパー・アグリー数(超醜い数)― 優先度付きキューを使った効率的な実装
スーパー・アグリー数とは?
スーパー・アグリー数(超醜い数)とは、そのすべての素因数が与えられた素数リスト primes(サイズ k)に含まれる正の整数のことです。
例えば、n = 12、primes = [2, 7, 13, 19] という条件の場合、出力は 32 になります。これは、[1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32] という並びが「12個目までのスーパー・アグリー数の列」に該当するためです。
本記事では、この問題を優先度付きキュー(最小ヒープ)を活用して効率よく解く方法を、C++のコード例とともに分かりやすく解説します。
解法のアプローチ
各素数ごとに「次の候補値(素数 × 既に確定したスーパー・アグリー数)」を管理し、ヒープによって常に最小の候補を取り出すのがポイントです。こうすることで、重複を排除しながら小さい順に数列を生成できます。
アルゴリズムの手順
num(候補値)、prime(素数)、idx(参照中のインデックス)の3つのフィールドを持つ構造体Dataを定義します。n が 1 の場合は 1 を返します。そうでなければ、サイズ n+1 の配列 v を作成し、すべて 1 で初期化します。
優先度付きキュー
pqを定義します。i を 0 から primes.size() − 1 までループし、各素数について
Data(primes[i], primes[i], 2)を作成して pq に挿入します。i を 2 から n まで以下を繰り返します:
pq の先頭要素を取り出して curr とし、キューから削除します。
val := curr.num とし、v[i] := val を代入します。
curr.num := curr.prime × v[curr.idx] を計算し、curr.idx を 1 増やしてから pq に戻します。
val == pq.top().num である限り、同じ処理を繰り返して重複する値を除去します。
v[n] を返します。
C++での実装例
以下の実装を見ると、処理の流れがより具体的につかめます。
#include <bits/stdc++.h>
using namespace std;
struct Data{
int num, prime, idx;
Data(int a, int b, int c){
num = a;
prime = b;
idx = c;
}
};
struct Comparator{
bool operator()(Data a, Data b){
return !(a.num < b.num);
}
};
class Solution {
public:
int nthSuperUglyNumber(int n, vector<int>& primes) {
if(n == 1)return 1;
vector <int> v(n + 1, 1);
priority_queue < Data, vector < Data >, Comparator > pq;
for(int i = 0; i < primes.size(); i++){
pq.push(Data(primes[i], primes[i], 2));
}
int x;
for(int i = 2; i <= n; i++){
Data curr = pq.top();
pq.pop();
int val = curr.num;
v[i] = val;
curr.num = curr.prime * v[curr.idx];
curr.idx++;
pq.push(curr);
while(val == pq.top().num){
curr = pq.top();
pq.pop();
curr.num = curr.prime * v[curr.idx];
curr.idx++;
pq.push(curr);
}
}
return v[n];
}
};
main(){
Solution ob;
vector<int> v = {2,7,13,19};
cout << (ob.nthSuperUglyNumber(12, v));
}
入力
12 [2,7,13,19]
出力
32
計算量とまとめ
この手法では、各ステップで最大 k 個の要素に対するヒープ操作が発生するため、時間計算量は O(n·k·log k)、必要なメモリは配列とヒープの分だけなので空間計算量は O(n + k) となります。
単純な全探索(すべての数を素因数分解して判定する方法)では非常に非効率ですが、優先度付きキューを使うことで、素数リストのサイズや n が大きくなっても実用的な速度で n 番目のスーパー・アグリー数を求められます。「複数のポインタをヒープで統合管理する」という発想は、マージ系の問題全般にも応用できるテクニックなので、ぜひ覚えておきましょう。
-
C++で質素数(Frugal Number)を判定する方法【サンプルコード付き】
この記事では、正の整数 N が与えられたときに、その数が質素数(Frugal Number)であるかどうかを判定するプログラムを C++ で作成する方法を解説します。 質素数とは? 質素数(FRUGAL NUMBER)とは、その数自身の桁数が、素因数分解による表現の桁数よりも厳密に大きい数のことです。 例:625 の場合 625 を素因数分解すると 54 となります。 625 自身の桁数:3 桁 54 の表現の桁数:2 桁 3 は 2 よりも厳密に大きいため、625 は質素数です。 最初のいくつかの質素数:125、128、243、256、343、512、625 など 問題を理解するための具
-
C++で五胞体数(ペンタトープ数)を求める方法
五胞体数とは? 五胞体数(ペンタトープ数)は、パスカルの三角形の第5の対角線上に現れる数列として知られています。この数列を定義するには、パスカルの三角形に少なくとも5つの数が必要となるため、数列の最初の数はパスカルの三角形の第4行である 1 4 6 4 1 から始まります。 本チュートリアルでは、n番目の五胞体数を求める方法を解説します。まずは具体的な例を見てみましょう。 入力 : 1出力 : 1入力 : 4出力 : 35 以下の図から出力を確認できます。 この問題は数列に関するものなので、解法ではまず数列のパターンを見つけることから始めます。 解法のアプローチ このプログラムでは、数列の