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

最大和増加部分列とは?動的計画法による求め方とC++実装例を解説

最大和増加部分列(Maximum Sum Increasing Subsequence)とは、与えられた整数列の中から選んだ部分列のうち、すべての要素が増加順に並んでおり、かつ総和が最大となるもののことです。

この問題は動的計画法(DP)を用いて解くのが一般的です。基本の考え方は、「配列の各位置 i に対して、arr[i] で終わる最大和増加部分列を記録しておく」というものです。i より手前にある各 j について「arr[j] < arr[i] であり、その時点で総和が最大となる部分列」を選び、その末尾に arr[i] を付け足すことで、L[i] を順次構築していきます。

入力と出力

入力:整数列 {3, 2, 6, 4, 5, 1}
出力:総和が最大となる増加部分列 {3, 4, 5}

この例では {3, 6}(総和 9)や {2, 4, 5}(総和 11)なども増加部分列ですが、{3, 4, 5} の総和 12 が最大であるため、これが答えになります。

アルゴリズム

maxSumSubSeq(array, n)

入力:数値の列 array、要素数 n
出力:総和が最大となる増加部分列

Begin
    大きさ n の「配列の配列」subSeqLen を用意する
    subSeqLen[0] に arr[0] を追加する

    for i = 1 to n-1 do
        for j = 0 to i-1 do
            if arr[i] > arr[j] かつ subSeqLen[i] の総和 < subSeqLen[j] の総和 then
                subSeqLen[i] := subSeqLen[j]
            end if
        end for
        // 部分列は必ず arr[i] で終わる
        subSeqLen[i] に arr[i] を追加する
    end for

    res := subSeqLen[0]

    subSeqLen のすべての要素について
        if subSeqLen[i] の総和 > res の総和 then
            res := subSeqLen[i]
        end if
    end for

    res の内容を出力する
End

計算量

要素同士を二重ループで比較するため、時間計算量は O(n²) です。サンプルコードでは比較のたびに findAllSum() で総和を再計算していますが、subSeqLen[i] の総和をあらかじめ別の配列にキャッシュしておけば、さらに高速化できます。

C++による実装例

#include <iostream>
#include <vector>
using namespace std;

// ベクトル内の全要素の総和を求める
int findAllSum(vector<int> arr) {
    int sum = 0;
    for (int i = 0; i < arr.size(); i++) {
        sum += arr[i];
    }
    return sum;
}

void maxSumSubSeq(int arr[], int n) {
    // arr[i] で終わる最大和増加部分列を格納する
    vector<vector<int> > subSeqLen(n);
    subSeqLen[0].push_back(arr[0]);

    for (int i = 1; i < n; i++) {          // インデックス 1 以降を順に処理
        for (int j = 0; j < i; j++) {      // すべての j(j < i)を確認
            if ((arr[i] > arr[j]) &&
                (findAllSum(subSeqLen[i]) < findAllSum(subSeqLen[j]))) {
                subSeqLen[i] = subSeqLen[j];   // 総和が大きい候補で置き換え
            }
        }
        subSeqLen[i].push_back(arr[i]);    // 部分列の末尾に arr[i] を追加
    }

    // 総和が最大となる部分列を探索
    vector<int> res = subSeqLen[0];
    for (int i = 0; i < subSeqLen.size(); i++) {
        if (findAllSum(subSeqLen[i]) > findAllSum(res)) {
            res = subSeqLen[i];
        }
    }

    // 結果の出力
    for (int i = 0; i < res.size(); i++) {
        cout << res[i] << " ";
    }
    cout << endl;
}

int main() {
    int arr[] = { 3, 2, 6, 4, 5, 1 };
    int n = 6;
    cout << "最大和部分列: ";
    maxSumSubSeq(arr, n);
    return 0;
}

実行結果

最大和部分列: 3 4 5

このように、各位置で終わる最良の部分列を順次構築していくことで、列全体の最大和増加部分列を求められます。最長増加部分列(LIS)と同じ動的計画法の枠組みで解ける応用問題なので、DPの理解を深める演習題材としてもおすすめです。

  1. C++で最長増加部分列の個数を求める方法

    問題概要ソートされていない整数の配列が与えられたとき、「最長増加部分列(LIS: Longest Increasing Subsequence)」の個数を求める問題を考えます。例えば、入力が [1, 3, 5, 4, 7] の場合を考えてみましょう。このとき最長増加部分列は [1, 3, 5, 7] と [1, 3, 4, 7] の2通りが存在するため、出力は 2 となります。解法のアプローチこの問題は動的計画法(DP)を用いて効率的に解くことができます。ポイントは、各インデックスについて「その要素を末尾とする最長増加部分列の長さ」と「その長さとなる部分列の個数」の2つを同時に管理することです

  2. C++で厳密に増加する部分配列の最大和を求めるアルゴリズム

    問題の概要n 個の整数からなる配列が与えられたとき、その中に存在する「厳密に増加する(strictly increasing)部分配列」の中で、要素の合計が最大となるものを求めます。例として、次のような配列を考えてみましょう。[1, 2, 3, 2, 5, 1, 7]この配列には、厳密に増加している部分配列が3つ存在します。{1, 2, 3}{2, 5}{1, 7}それぞれの合計は 6、7、8 となり、この中で最大となるのは {1, 7} の合計 8 です。解き方の考え方この問題は、現在の部分配列の合計(current_sum)とこれまでの最大合計(max_sum)を追跡しながら配列を一度だけ