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

【C++】指定範囲内の値を持つ配列要素の個数を求めるクエリ処理の実装方法

この問題では、配列 arr[]Q 個のクエリが与えられます。各クエリは次の2種類のいずれかです。

  • {1, L, R} … 範囲 [L, R] 内の値を持つ配列要素の個数を求める

  • {2, index, val}index の位置にある要素を val に更新する

本記事では、これらのクエリを効率的に処理するC++プログラムの作成方法を、2つのアプローチに分けて解説します。

問題の例

入力:

arr[] = {1, 5, 2, 4, 2, 2, 3, 1, 3}
Q = 3
Query = { {1, 4, 8},
          {2, 6, 5},
          {1, 1, 4}}

出力: 2 7

解説

クエリ1: 範囲 [4, 8] に含まれる配列要素を数えます。該当するのは 54 の2つなので、個数は 2 です。

クエリ2: arr[6]5 に更新します。更新後の配列は arr[] = {1, 5, 2, 4, 2, 2, 5, 1, 3} となります。

クエリ3: 範囲 [1, 4] に含まれる配列要素を数えます。該当するのは 1, 2, 4, 2, 2, 1, 3 の7つなので、個数は 7 です。

解法アプローチ1: 線形探索(単純な方法)

最もシンプルな解決策は、カウントのクエリが来るたびに配列を直接走査し、L ≤ 要素 ≤ R を満たす要素をすべて数える方法です。

実装例

#include <iostream>
using namespace std;
int countElementInRange(int arr[], int N, int L, int R){
    int ValueCount = 0;
    for (int i = 0; i < N; i++) {
       if (arr[i] >= L && arr[i] <= R) {
          ValueCount++;
       }
    }
    return ValueCount;
}
int main() {
    int arr[] = {1, 5, 2, 4, 2, 2, 3, 1, 3};
    int N = sizeof(arr) / sizeof(arr[0]);
    int Q = 3;
    int query[Q][3] = { {1, 4, 8},{2, 6, 5},{1, 1, 4}};
    for(int i = 0; i < Q; i++){
       if(query[i][0] == 1)
          cout<<"The count of array elements with value in given range is " <<countElementInRange(arr,N, query[i][1], query[i][2])<<endl;
       else if(query[i][0] == 2){
          cout<<"Updating Value \n";
          arr[query[i][1]] = query[i][2];
       }
    }
    return 0;
}

出力

The count of array elements with value in given range is 2
Updating Value
The count of array elements with value in given range is 7

計算量の分析

このアプローチでは、範囲カウントのクエリごとに配列全体を一度走査する必要があります。そのため、時間計算量は O(Q×N) となります。配列サイズ N やクエリ数 Q が大きくなると処理が遅くなるため、より効率的な方法が求められます。

解法アプローチ2: Binary Indexed Tree(Fenwick Tree)を使う方法

より効率的な解決策として、Binary Indexed Tree(BIT、別名 Fenwick Tree)というデータ構造を利用する方法があります。配列の「値」を木のインデックスとして扱い、各値の出現回数を木に格納することで、組み込みの getSum(累積和取得)操作を使って範囲内の要素数を高速に求められます。

範囲 [L, R] 内の要素数は、次の式で計算できます。

ElementCount[L, R] = getSum(R) - getSum(L - 1)

要素の更新時には、古い値の出現回数を -1、新しい値の出現回数を +1 することで木を正しい状態に保ちます。

実装例

#include <iostream>
using namespace std;
class BinaryIndTree {
    public:
        int* BIT;
        int N;
        BinaryIndTree(int N) {
            this->N = N;
            BIT = new int[N];
            for (int i = 0; i < N; i++) {
                BIT[i] = 0;
            }
        }
        void update(int index, int increment) {
            while (index < N) {
                BIT[index] += increment;
                index += (index & -index);
            }
        }
        int calcSum(int index) {
            int sum = 0;
            while (index > 0) {
                sum += BIT[index];
                index -= (index & -index);
            }
            return sum;
        }
};
void UpdateValue(int* arr, int n, int index, int val, BinaryIndTree* fenwickTree){
    int removedElement = arr[index];
    fenwickTree->update(removedElement, -1);
    arr[index] = val;
    fenwickTree->update(val, 1);
}
int countElementInRange(int* arr, int n, int L, int R, BinaryIndTree* fenwickTree) {
    return fenwickTree->calcSum(R) - fenwickTree->calcSum(L - 1);
}
int main() {
    int arr[] = { 1, 5, 2, 4, 2, 2, 3, 1, 3 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int Q = 3;
    int query[Q][3] = { {1, 4, 8},{2, 6, 5},{1, 1, 4}};
    int N = 100001;
    BinaryIndTree* fenwickTree = new BinaryIndTree(N);
    for (int i = 0; i < n; i++)
        fenwickTree->update(arr[i], 1);
    for(int i = 0; i < Q; i++){
        if(query[i][0] == 1)
            cout<<"The count of array elements with value in given range is "<<countElementInRange(arr, n, query[i][1], query[i][2], fenwickTree)<<endl;
        else if(query[i][0] == 2){
            cout<<"Updating Value \n";
            UpdateValue(arr, n, query[i][1], query[i][2], fenwickTree);
        }
    }
    return 0;
}

出力

The count of array elements with value in given range is 2
Updating Value
The count of array elements with value in given range is 7

まとめ

Fenwick Tree を使ったアプローチでは、要素の更新も範囲の集計もそれぞれ O(log N) で処理できます。初期構築を含めた全体の時間計算量は O((N + Q) log N) となり、線形探索の O(Q×N) と比較して大幅な高速化が期待できます。なお、配列の値の取り得る範囲が非常に大きい場合は、座標圧縮(値を順位に変換する前処理)を組み合わせることで、BIT のメモリ使用量を抑えられる点も覚えておくとよいでしょう。

  1. C++で配列要素の階乗の最大公約数(GCD)を求める方法

    N個の要素を持つ配列Aが与えられたとき、配列内のすべての要素の階乗の最大公約数(GCD)を求めることを考えます。例えば、配列の要素が {3, 4, 8, 6} の場合、各要素の階乗は 3! = 6、4! = 24、8! = 40320、6! = 720 となり、これらのGCDは 6 になります。解法のポイントここで重要な数学的な性質があります。2つの数のGCDとは、両方の数を割り切る最大の数のことです。階乗の場合、小さい数の階乗は必ず大きい数の階乗を割り切ることができます。つまり、2つの階乗のGCDは、小さい方の数の階乗そのものになります。例えば、3! と 5! のGCDを考えると、3! =

  2. 配列の全要素を乗算するC++プログラムの解説

    整数型の要素を持つ配列が与えられたとき、配列内のすべての要素を掛け合わせ、その積を表示することを考えます。本記事では、この問題をC++(C言語スタイルのコード)で解く方法を、アプローチ、アルゴリズム、サンプルコード、実行結果まで順を追って解説します。 例 入力: arr[]={1,2,3,4,5,6,7} 出力: 1 x 2 x 3 x 4 x 5 x 6 x 7 = 5040 入力: arr[]={3, 4, 6, 2, 7, 8, 4} 出力: 3 x 4 x 6 x 2 x 7 x 8 x 4 = 32256 解き方のアプローチ この問題は、累積用の一時変数を用意し、配列の要素を先頭