C++で数nを1にするのに必要な最小操作回数を求めるプログラム
ある正の整数 n が与えられます。この n に対して、次のいずれかの操作を何度でも実行できるものとします。
nが2で割り切れるとき、nをn/2に置き換えるnが3で割り切れるとき、nを2n/3に置き換えるnが5で割り切れるとき、nを4n/5に置き換える
これらの操作を繰り返し、n を1にするまでに必要な最小の操作回数を求めてください。どうしても1にできない場合は -1 を返します。
たとえば、入力が n = 10 のとき、出力は 4 になります。具体的な手順は次のとおりです。
n/2の操作で 10 → 54n/5の操作で 5 → 4n/2の操作で 4 → 2n/2の操作で 2 → 1
解法の考え方
この問題は貪欲法(グリーディ法)で解くことができます。n を1にするには、素因数である2・3・5をすべて取り除く必要があります。したがって、n が 2^a × 3^b × 5^c の形で表せない場合、答えは -1 になります。
また、各素因数を取り除くのに必要な操作回数は次のように考えられます。
- 2で割る場合: 操作「
n → n/2」をそのまま使えるので 1回 - 3で割る場合: 「
n → 2n/3」→「n/2」の2段階でn/3に到達できるので 2回(奇数のnでは2n/3が必ず偶数になるため有効) - 5で割る場合: 「
n → 4n/5」→「n/2」→「n/2」の3段階でn/5に到達できるので 3回
アルゴリズムの手順
この問題を解くために、以下の手順に従います。
m := 0
n が 1 ではない間、以下を繰り返す:
n mod 2 が 0 の場合:
n := n / 2
m を 1 増やす
そうでなく n mod 3 が 0 の場合:
n := n / 3
m := m + 2
そうでなく n mod 5 が 0 の場合:
n := n / 5
m := m + 3
それ以外の場合:
m := -1
ループを抜ける
m を返すC++での実装例
理解を深めるために、実際の実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
int solve(int n) {
int m = 0;
while (n != 1) {
if (n % 2 == 0) {
n = n / 2;
m++;
}
else if (n % 3 == 0) {
n = n / 3;
m += 2;
}
else if (n % 5 == 0) {
n = n / 5;
m += 3;
}
else {
m = -1;
break;
}
}
return m;
}
int main() {
int n = 10;
cout << solve(n) << endl;
}入力
10
出力
4
計算量
ループの各反復で n は必ず減少していくため、時間計算量は O(log n)、空間計算量は O(1) となり、非常に効率的です。
-
サイズ d の正十二角形を作れる組み合わせの数を求める C++ プログラム
問題概要 整数 d が与えられたとします。ここで、一辺の長さが 1 の正方形タイルと正三角形タイルが無限枚あるものと考えます。これらのタイルを組み合わせて、一辺の長さが d の正十二角形(12 辺形)を作るとき、その作り方が何通りあるかを求めるのがこの問題です。答えが非常に大きくなる場合は、998244353 で割った余りを返します。 アプローチ この問題は、二項係数を利用することで効率的に解くことができます。結論から言うと、求めるべき答えは C(2d−1, d−1)、すなわち「2d−1 個の中から d−1 個を選ぶ組み合わせの総数」です。 階乗を直接計算すると値が急激に大きくなりオーバー
-
【C++】バイナリ行列をすべて0に変換するための最小操作回数を求めるプログラム
問題概要0と1のみから構成されるバイナリ行列が与えられます。使用できる操作は「任意の1つのセルを選び、そのセル自身と上下左右の隣接するセル(存在する場合のみ)をすべて反転(0→1、1→0)する」というものです。この操作を繰り返して行列の全要素を0にするために必要な最小操作回数を求めてください。どのように操作してもすべて0にできない場合は -1 を返します。入力例{{0, 0}, {1, 0}}これは次のような2×2の行列です。0010出力3この場合、必要な操作回数は3回となります。解法のアプローチこの問題は、行列の状態をビットマスク(整数)として表現し、幅優先探索(BFS)で最短操作回数を求め