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

C++で部分配列の合計が偶数になる個数を求める方法

要素数 N の配列 arr[] が与えられたとき、合計が偶数となる部分配列(連続部分配列)の個数を求める問題について解説します。

問題の例

入力

arr[] = {2, 1, 3, 4, 2, 5}

出力

28

この配列において、合計が偶数となる連続部分配列は全部で 28 個存在します。(元記事の列挙には非連続の部分配列(部分列)が混在していましたが、ここでは標準的な定義である「連続部分配列」としてカウントしています。)

解法 1:ブルートフォース法(全探索)

最も直感的な方法は、すべての部分配列の合計を計算し、偶数かどうかを判定することです。計算量は O(N^2) となります。

アルゴリズム

  1. 開始インデックス i を 0 から N-1 まで動かす
  2. 終了インデックス ji から N-1 まで動かし、部分配列 arr[i...j] の合計を順次加算する
  3. 合計が偶数(sum % 2 == 0)ならカウントを増やす

C++ 実装例

#include <iostream>
using namespace std;

// 部分配列の偶数合計カウント(O(N^2))
int countEvenSumSubArrayBruteForce(int arr[], int n) {
    int evenSumCount = 0;
    for (int i = 0; i < n; i++) {
        int sum = 0;
        for (int j = i; j < n; j++) {
            sum += arr[j];
            if (sum % 2 == 0) {
                evenSumCount++;
            }
        }
    }
    return evenSumCount;
}

int main() {
    int arr[] = {2, 1, 3, 4, 2, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    cout << "偶数合計の部分配列の数: " << countEvenSumSubArrayBruteForce(arr, n) << endl;
    // 出力: 28
    return 0;
}

解法 2:累積和の偶奇を利用した最適解(O(N))

部分配列 arr[i...j] の合計は、累積和を用いて prefix[j+1] - prefix[i] と表せます。この差が偶数になる条件は、prefix[j+1]prefix[i] の偶奇(パリティ)が**同じ**場合です。

したがって、累積和の値が「偶数」になるインデックスの数を evenCount、「奇数」になるインデックスの数を oddCount とすると、答えは以下の組み合わせの和になります。 \[ \binom{evenCount}{2} + \binom{oddCount}{2} = \frac{evenCount \times (evenCount - 1)}{2} + \frac{oddCount \times (oddCount - 1)}{2} \]

初期状態(要素なし)の累積和 0 は偶数なので、evenCount は 1 から始めます。

C++ 実装例

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

// 部分配列の偶数合計カウント(O(N))
long long countEvenSumSubArrayOptimized(const vector<int>& arr) {
    long long evenCount = 1; // 累積和 0(空部分配列)は偶数
    long long oddCount = 0;
    int prefixParity = 0;    // 0: 偶数, 1: 奇数

    for (int x : arr) {
        prefixParity = (prefixParity + (x & 1)) & 1; // 偶奇のみ更新
        if (prefixParity == 0) {
            evenCount++;
        } else {
            oddCount++;
        }
    }
    // nC2 = n * (n - 1) / 2
    return (evenCount * (evenCount - 1) / 2) + (oddCount * (oddCount - 1) / 2);
}

int main() {
    vector<int> arr = {2, 1, 3, 4, 2, 5};
    cout << "偶数合計の部分配列の数: " << countEvenSumSubArrayOptimized(arr) << endl;
    // 出力: 28
    return 0;
}

計算量の比較

手法時間計算量空間計算量備考
ブルートフォースO(N²)O(1)N ≤ 10³ 程度まで現実的
累積和の偶奇(最適)O(N)O(1)大規模データ(N ≤ 10⁵〜10⁶)に対応可能

まとめ

  • 部分配列の合計が偶数 ⇔ 区間の両端の累積和の偶奇が一致
  • 累積和の偶奇の出現回数を数えるだけで、全区間を列挙せずに答えを導出できる
  • 競プロや実務では O(N) 解法 が標準的です
  1. C++プログラムで数値の偶数の約数の合計を求める方法

    このプログラムは、与えられた整数のすべての偶数の約数を見つけ、それらの合計を計算して画面に出力するものです。 実行例 入力 : 30 偶数の約数 : 2+6+10+30 = 48 出力 : 48 この問題を解くアプローチは、大きく分けて2つあります。 方法1:すべての約数を列挙して偶数のみを合計する まず対象の数値の約数をすべて求め、その中から偶数のものだけを取り出して合計します。この方法はシンプルで理解しやすい一方、約数を1つずつ確認するため、数値が大きくなると計算量が増えるという欠点があります。 方法2:素因数分解の公式を利用する より効率的なのが、素因数分解を利用した数学的な公式を使う方

  2. 【C++】ある数の偶数の素因数の合計を効率的に求める方法

    はじめにこの記事では、ある整数の「偶数の素因数」の合計を効率的に求める方法を解説します。例として、n = 480 という数を考えてみましょう。480 を素因数分解すると、2、2、2、2、2、3、5 となります。このうち偶数である素因数は 2 のみなので、その合計は 2+2+2+2+2 = 10 になります。一見すると、すべての因数を列挙して偶数かどうか判定する必要があるように思えますが、実はもっとシンプルな方法で解けます。解法のポイント偶数の素因数は 2 しか存在しない、という点が重要です。これを利用すると、次の手順で問題を解くことができます。数が 2 で割り切れる間、そのたびに合計に 2 を