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

C++で解く「最大和減少部分列」問題 ― 動的計画法による実装方法を徹底解説

はじめに

本記事では、N個の整数からなる配列 arr[] が与えられたとき、その中から厳密に減少する部分列を抜き出し、要素の合計が最大になる値(最大和減少部分列)をC++で求める方法を解説します。

問題の概要

配列の中から要素を選び、選んだ順序が左から右へ単調減少となるように部分列を作ります。そのとき、部分列の要素の合計として考えられる最大値を求めるのが目的です。

具体例で確認してみましょう。

入力例

arr[] = {3, 1, 6, 10, 5, 2, 9}

出力例

17

解説

この場合、合計が最大となる減少部分列は {10, 5, 2} です。10 + 5 + 2 = 17 が答えとなります。

アプローチ:動的計画法(DP)

この問題は動的計画法を使うことで効率的に解くことができます。基本的な考え方は以下の通りです。

  • maxSum[i] を「インデックス i の要素を末尾とする減少部分列の合計の最大値」と定義します。
  • 初期値として maxSum[i] = arr[i] とします(自分自身のみの部分列)。
  • i より前の各要素 j について、arr[i] < arr[j](減少関係が成り立つ)かつ maxSum[j] + arr[i] が現在の maxSum[i] より大きければ更新します。

これを式で表すと次のようになります。

maxSum[i] = arr[i] + max(maxSum[0 … i-1])(ただし arr[j] > arr[i] を満たす j のみ)

最終的な答えは、配列 maxSum[] の中の最大値です。

計算量は二重ループを使用するため O(N²)、必要な記憶領域は O(N) となります。

C++での実装例

以下は、上記のアルゴリズムを実装したサンプルプログラムです。

#include <iostream>
using namespace std;

int findMaxSumDecSubSeq(int arr[], int N){
    int maximumSum = 0;
    int maxSum[N];

    // 初期化:各要素自身が部分列の先頭になりうる
    for (int i = 0; i < N; i++)
        maxSum[i] = arr[i];

    // 動的計画法による更新
    for (int i = 1; i < N; i++)
        for (int j = 0; j < i; j++)
            if (arr[i] < arr[j] && maxSum[i] < maxSum[j] + arr[i])
                maxSum[i] = maxSum[j] + arr[i];

    // 最大値を求める
    for (int i = 0; i < N; i++)
        if (maximumSum < maxSum[i])
            maximumSum = maxSum[i];

    return maximumSum;
}

int main(){
    int arr[] = { 5, 4, 100, 3, 2, 101, 1 };
    int N = sizeof(arr) / sizeof(arr[0]);
    cout << "最大和減少部分列の合計: " << findMaxSumDecSubSeq(arr, N);
    return 0;
}

実行結果

最大和減少部分列の合計: 106

処理の流れのポイント

  1. 初期化: maxSum[] を配列 arr[] の各要素で初期化します。これは「その要素だけからなる長さ1の部分列」を意味します。
  2. 遷移: 各インデックス i に対し、それより前のすべての j を調べ、arr[j] > arr[i] であれば maxSum[j] に arr[i] を加えた値で更新できるかチェックします。
  3. 答えの抽出: すべての maxSum[i] の中で最も大きい値が、求める最大和となります。

上記の例では、100 → 3 → 2 → 1 という減少部分列の合計 106 が最大となることが確認できます。

まとめ

最大和減少部分列の問題は、最長増加部分列(LIS)と同じ発想で解ける典型的な動的計画法の応用問題です。maxSum 配列を用いて「各位置を末尾とする部分列の最大和」を順に更新していくことで、O(N²) の時間計算量で確実に答えを求められます。競技プログラミングやアルゴリズム学習の基礎として、ぜひ理解しておきましょう。

  1. C++のプレフィックス和(累積和)を活用してO(n)で最大部分配列和を求める方法

    問題概要 正の整数と負の整数が混在する配列が与えられたとき、その配列の中で合計値が最大となる部分配列(連続した要素の並び)の合計を求める問題です。 例 入力配列が {-12, -5, 4, -1, -7, 1, 8, -3} の場合、合計が最大になる部分配列は {1, 8} となるため、出力は 9 になります。 アルゴリズム この問題は、プレフィックス和(累積和)を利用することで O(n) の時間計算量で効率的に解くことができます。考え方の核心は、「ある位置 i で終わる部分配列の合計の最大値」は「prefix_sum[i] から、それ以前に現れた最小の累積和を引いた値」で表せるという点です

  2. C++で配列の最大平衡和(イクリブリアム・サム)を求める方法

    問題概要配列 arr[] が与えられたとき、あるインデックス i における「接頭辞和(プレフィックスサム)」と「接尾辞和(サフィックスサム)」が一致する値の中から、最大値を見つけるのがこの問題の目的です。この一致する値は「平衡和(イクリブリアム・サム)」と呼ばれます。例入力配列が以下の場合を考えてみましょう。Arr[] = {1, 2, 3, 5, 3, 2, 1}このとき出力は 11 になります。その理由は次の通りです。接頭辞和 = arr[0..3] = 1 + 2 + 3 + 5 = 11接尾辞和 = arr[3..6] = 5 + 3 + 2 + 1 = 11インデックス 3 を境にし