サブセットの総和 – C++での動的計画法による効率的な解法
問題概要
この問題では、サイズ 2n の配列 arr[] が与えられます。目的は、動的計画法(DP)を用いて「サブセットの総和(Sum over Subsets)」を求めるプログラムを作成することです。
具体的には、次の関数 F(x) を計算します。
F(x) = Σ Ai (ただし x & i == i を満たすすべての i に対する和)
つまり、i が x のビット単位での部分集合であるような Ai の合計を求めるという意味です。
入力例と出力例
入力: A[] = {5, 7, 1, 9}, n = 2
出力: 5 12 6 22
説明: n = 2 のとき、x は 0、1、2、3 の 4 通りの値を取ります。それぞれの関数値は以下のように計算できます。
F(0) = A0 = 5
F(1) = A0 + A1 = 5 + 7 = 12
F(2) = A0 + A2 = 5 + 1 = 6
F(3) = A0 + A1 + A2 + A3 = 5 + 7 + 1 + 9 = 22
解法のアプローチ
この問題を動的計画法で解くには、各マスク(mask)に着目し、それぞれのマスクについてビット単位の部分集合を効率よく求めます。部分集合の和を DP テーブルに記録しておくことで、重複する計算を大幅に削減できます。素朴な全探索では、ビットが立っているかどうかにかかわらず、各インデックスが 2n 個のマスクから何度も参照されてしまいます。
i 番目のビットの状態に応じて、次のような漸化式を立てます。
i 番目のビットが立っている場合:
DP(mask, i) = DP(mask, i-1) + DP(mask ^ 2i, i-1)
i 番目のビットが立っていない場合:
DP(mask, i) = DP(mask, i-1)
この手法は「SOS DP(Sum over Subsets DP)」として知られており、時間計算量は O(n ・ 2n)、空間計算量も O(n ・ 2n) となります。
ソリューションの実装例(C++)
#include <iostream>
using namespace std;
const int N = 1000;
void SumOverSubsets(int a[], int n) {
int sum[1 << n] = {0};
int DP[N][N];
for (int i = 0; i < (1 << n); i++) {
for (int j = 0; j < n; j++) {
if (i & (1 << j)) {
if (j == 0)
DP[i][j] = a[i] + a[i ^ (1 << j)];
else
DP[i][j] = DP[i][j - 1] + DP[i ^ (1 << j)][j - 1];
} else {
if (j == 0)
DP[i][j] = a[i];
else
DP[i][j] = DP[i][j - 1];
}
}
sum[i] = DP[i][n - 1];
}
for (int i = 0; i < (1 << n); i++)
cout<<sum[i]<<"\t";
}
int main() {
int A[] = {5, 7, 1, 9};
int n = 2;
cout<<"The sum over subsets is \t";
SumOverSubsets(A, n);
return 0;
}出力
The sum over subsets is 5 12 6 22
-
C++でアリコート和(Aliquot Sum)を計算する方法
本記事では、アリコート和(Aliquot Sum)とは何かを解説します。アリコート和とは、ある数 n の約数のうち、n 自身を除いたすべての約数の総和のことです。例えば、数値が 20 の場合、その約数は (1, 2, 4, 5, 10) となるため、アリコート和は 22 になります。興味深い点として、アリコート和がその数自身と等しくなる場合、その数は「完全数」と呼ばれます。例えば 6 の場合、約数は (1, 2, 3) であり、アリコート和は 1 + 2 + 3 = 6 となるため、6 は完全数です。それでは、以下のアルゴリズムを使ってアリコート和を求める方法を見ていきましょう。アルゴリズムg
-
動的計画法を用いて最適なかっこ付け(行列連鎖乗算)を求めるC++プログラム
本記事では、動的計画法(Dynamic Programming)を用いて、行列連鎖乗算における最適なかっこ付け(Optimal Parenthesization)を求めるC++プログラムを紹介します。 複数の行列を連続して掛け合わせる場合、計算の順序(どのペアから先に掛けるか)によって必要なスカラー乗算の回数が大きく変化します。動的計画法を活用すれば、すべての順序を総当たりすることなく、乗算回数が最小となる分割位置を効率的に求めることができます。 アルゴリズム まず、使用する変数と配列の意味を確認しておきましょう。 a[i][j]:行列 A[i]A[i+1]…A[j](= A[i..j])を