C++で解く範囲合計クエリ(不変配列)― 累積和による効率的な実装
整数の配列が与えられたとき、インデックス i から j までの範囲に含まれる要素の合計を求めることを考えます。この問題には2つの重要なポイントがあります。1つ目は、配列が不変(イミュータブル)であるため要素が一切変更されないこと、2つ目は、同じ種類のクエリが複数回実行されることです。そのため、大量のクエリが発生しても高速に処理できるよう、実行時間を考慮する必要があります。
例えば、配列が A = [5, 8, 3, 6, 1, 2, 5] のとき、クエリ (A, 0, 3) に対する答えは 5 + 8 + 3 + 6 = 22 となります。
解法のアプローチ:累積和(プレフィックスサム)
この問題を効率的に解くために、以下の手順に従います。
- 補助配列 B を用意します。B[i] には、インデックス 0 から i までの要素の累積合計を格納します。
- 範囲合計のクエリに対しては、B[j] − B[i − 1] を計算して返します。
この手法では、前処理に O(n) の時間がかかりますが、その後の各クエリは O(1) で答えられるため、クエリの数が多い場合に非常に有効です。毎回素朴に範囲内の要素を足し合わせる方法では、1回のクエリに O(n) かかってしまい、クエリが大量にある場合に非効率になります。
実装例
理解を深めるために、以下の C++ の実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
class NumArray {
public:
vector <int> pre;
NumArray(vector<int>& nums) {
pre.clear();
int n = nums.size();
pre.resize(n);
for(int i = 0; i < n; i++){
if(i == 0)pre[0] = nums[0];
else
pre[i] = pre[i - 1] + nums[i];
}
}
int sumRange(int i, int j) {
if(i == 0)return pre[j];
return pre[j] - pre[i - 1];
}
};
main(){
vector<int> v = {5,8,3,6,1,2,5};
NumArray na(v);
cout<<na.sumRange(0,2)<<endl;
cout<<na.sumRange(2,5)<<endl;
cout<<na.sumRange(0,5)<<endl;
}入力
[5,8,3,6,1,2,5] で初期化 sumRange(0,2) を呼び出し sumRange(2,5) を呼び出し sumRange(0,5) を呼び出し
出力
16 12 25
出力の解説
- sumRange(0, 2) → 5 + 8 + 3 = 16
- sumRange(2, 5) → 3 + 6 + 1 + 2 = 12
- sumRange(0, 5) → 5 + 8 + 3 + 6 + 1 + 2 = 25
計算量
- 前処理(コンストラクタ):O(n)
- 各クエリ(sumRange):O(1)
- 空間計算量:O(n)(累積和配列ぶん)
このように、累積和を事前に計算しておくことで、不変配列に対する範囲合計クエリを定数時間で処理できます。なお、配列の要素が更新される可能性がある場合は、セグメント木や Binary Indexed Tree(BIT)などのデータ構造を検討するとよいでしょう。
-
更新なしの範囲合計クエリをC++で高速に解く方法|累積和の活用
本記事では、配列のインデックスiからjまでの要素の合計を求める方法を解説します。これはいわゆる「範囲合計クエリ(レンジクエリ)」と呼ばれる典型的な問題です。 最も単純な方法は、インデックスiからjまでループを回して順番に合計を足していくことです。しかし、この種の範囲クエリは複数回実行されることが前提となるため、クエリごとに毎回ループで計算していると処理時間が大きくなってしまいます。 そこで有効なのが累積和を事前に計算しておく手法です。累積和を前計算しておけば、以降の範囲合計はどの範囲でも定数時間O(1)で求められます。具体的なアルゴリズムを見ていきましょう。 アルゴリズム rangeSum(
-
C++の配列パズル:減算演算子を使わずに「自分以外の要素の合計」を求める方法
今回は、配列に関する興味深い問題を紹介します。n個の要素を持つ配列が与えられ、それをもとに同じくn個の要素を持つ別の配列を作成します。ただし、新しい配列のi番目には、元の配列のi番目の要素を除いたすべての要素の合計を格納します。さらに重要な制約として、減算演算子(-)を使用してはいけないという条件が課されています。 問題のポイント もし減算が使えるのであれば、話は簡単です。まず全要素の合計を求めておき、そこからi番目の要素を引いた値を新しい配列のi番目に格納すればよいだけです。しかし、この問題では減算が禁止されているため、別のアプローチが必要になります。 そこで、各位置i(0〜n-1)について