C++でBinary Indexed Tree(BIT)を使って最大和増加部分列を求める方法
この記事では、N個の要素からなる配列 arr[] が与えられたとき、C++のBinary Indexed Tree(BIT、フェニック木)を活用して、最大和増加部分列(Maximum Sum Increasing Subsequence)を求めるプログラムの作成方法を解説します。
問題例で理解しよう
入力
arr[] = {4, 1, 9, 2, 3, 7}出力
13
解説
この場合、最大の和を持つ増加部分列は「1, 2, 3, 7」であり、その合計は 1 + 2 + 3 + 7 = 13 となります。
解法のアプローチ
この問題を効率的に解くために、Binary Indexed Tree(BIT)を使用します。基本的な考え方は以下の通りです。
手順1: 配列の各値を座標圧縮(値のランク付け)し、BITのインデックスにマッピングします。
手順2: 配列を左から順に走査し、各要素について「その要素より小さい値で終わる増加部分列の最大和」をBITから取得します。
手順3: 取得した最大和に現在の要素の値を加えたものを、BITの該当位置に更新(max演算)していきます。
通常の動的計画法(DP)では O(N²) の計算量が必要ですが、BITを用いることで O(N log N) まで計算量を削減できます。
C++実装例
以下は、この解法の動作を示すプログラムです。
#include <bits/stdc++.h>
using namespace std;
// indexまでの範囲から最大の和を取得する関数
int calcMaxSum(int BITree[], int index){
int maxSum = 0;
while (index > 0){
maxSum = max(maxSum, BITree[index]);
index -= index & (-index);
}
return maxSum;
}
// BITを更新する関数(最大値で更新)
void updateBIT(int BITree[], int newIndex, int index, int val){
while (index <= newIndex){
BITree[index] = max(val, BITree[index]);
index += index & (-index);
}
}
// 最大和増加部分列を求めるメイン関数
int maxSumIS(int arr[], int n){
int index = 0, maxSum;
map<int, int> arrMap;
// 座標圧縮:値をBITのインデックスにマッピング
for (int i = 0; i < n; i++){
arrMap[arr[i]] = 0;
}
for (map<int, int>::iterator it = arrMap.begin(); it != arrMap.end(); it++){
index++;
arrMap[it->first] = index;
}
int* BITree = new int[index + 1];
for (int i = 0; i <= index; i++){
BITree[i] = 0;
}
// 各要素について最大和を計算し、BITを更新
for (int i = 0; i < n; i++){
maxSum = calcMaxSum(BITree, arrMap[arr[i]] - 1);
updateBIT(BITree, index, arrMap[arr[i]], maxSum + arr[i]);
}
return calcMaxSum(BITree, index);
}
int main() {
int arr[] = {4, 6, 1, 9, 2, 3, 5, 8};
int n = sizeof(arr) / sizeof(arr[0]);
cout << "Binary Indexed Treeを用いた最大和増加部分列の和: " << maxSumIS(arr, n);
return 0;
}出力
Binary Indexed Treeを用いた最大和増加部分列の和: 19
コードのポイント
この実装における重要なポイントを整理します。
calcMaxSum関数: BITの特性を利用して、指定したインデックスまでの範囲に含まれる最大の部分列和を O(log N) で取得します。インデックスの最下位ビットを減算しながら親ノードを辿っていくのがポイントです。
updateBIT関数: 値を更新する際、通常のBITのように加算するのではなく、max演算で更新します。これにより、各区間の最大和が常に正しく保持されます。
座標圧縮: 配列の値をそのままBITのインデックスに使うと、値の範囲が大きい場合にメモリを無駄に消費します。mapを使って値を1から連番に圧縮することで、配列サイズを要素数 N に抑えられます。
まとめ
Binary Indexed Treeを応用することで、最大和増加部分列の問題を O(N log N) の時間計算量で効率的に解くことができます。BITは区間和の計算でよく知られていますが、このようにmax演算と組み合わせることで、さまざまな最適化問題にも応用できる強力なデータ構造です。競技プログラミングでも頻出のテクニックなので、ぜひマスターしておきましょう。
-
C++で二分木における2つの葉ノード間の最大パス合計を求める方法
問題の概要 この問題では、各ノードが値を持つ二分木が与えられます。私たちのタスクは、二分木における2つの葉ノード(リーフノード)間の最大パス合計を求めるプログラムを作成することです。 ここで求めるのは、値の合計が最大になるような、ある葉ノードから別の葉ノードへのパスです。この最大合計パスには、ルートノードが含まれる場合もあれば、含まれない場合もあります。 二分木(Binary Tree)とは、各ノードが最大2つの子ノードを持つことができる木構造のデータ構造です。それぞれの子ノードは「左の子(left child)」と「右の子(right child)」と呼ばれます。 具体例 以下のような二分木
-
C++で二分木の最大垂直和を求める方法
はじめに二分木が与えられたとき、垂直順序走査における各垂直列のノード値の合計を計算し、その中から最大値を求めて出力するのが本記事の課題です。例として、以下のような二分木を考えてみましょう。この二分木を垂直順序走査すると、各列の合計は次のようになります。4 2 1 + 5 + 6 = 12 3 + 8 = 11 7 9各列の合計の中で最大となるのは 12 です。アルゴリズムの考え方アプローチはシンプルです。幅優先探索(BFS)を用いて垂直順序走査を行い、各ノードに水平距離を割り当てます。ルートの水平距離を 0 とし、左に移動するごとに -1、右に移動するごとに +1 とします。同じ水平距離を持つ