C++で重み付きランダム選択を実装する方法
正の整数の配列 w が与えられ、w[i] がインデックス i の重みを表しているとします。このとき、重みに比例した確率でインデックスをランダムに選択する関数 pickIndex() を定義する必要があります。
例えば、入力が [1, 3] の場合、pickIndex() を5回呼び出すと、結果は 0, 1, 1, 1, 0 のようになります。インデックス 1 の重みが 3 であるため、インデックス 1 が選ばれる確率はインデックス 0 の3倍になります。
解決策のアプローチ
この問題を解くために、以下の手順に従います。
- 配列
vを定義します。 - コンストラクタで以下のように初期化します。
n := w[0]とします。- i を 1 から w のサイズまでループします。
w[i] := w[i] + w[i - 1](累積和を計算)n := w[i]
v = wとして累積和の配列を保存します。
pickIndex() は以下のように動作します。
- 乱数 r を生成し、
r mod v の最後の要素を計算します。 - v の中から r 以上の最小の要素を二分探索で見つけ、その位置を返します。
この手法のポイントは、累積和配列を使うことで、各インデックスが選ばれる確率がその重みに正確に比例することを保証できる点です。また、upper_bound による二分探索を用いることで、選択処理を O(log n) の時間計算量で効率的に行えます。
実装例
以下の実装を見て、より深く理解しましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int n;
vector <int> v;
Solution(vector<int>& w) {
srand(time(NULL));
n = w[0];
for(int i = 1; i < w.size(); i++){
w[i] += w[i - 1];
n = w[i];
}
v = w;
}
int pickIndex() {
return upper_bound(v.begin(), v.end(), rand() % v.back()) - v.begin();
}
};
main(){
vector<int> v = {1,3};
Solution ob(v);
cout << (ob.pickIndex()) << endl;
cout << (ob.pickIndex()) << endl;
cout << (ob.pickIndex()) << endl;
cout << (ob.pickIndex()) << endl;
cout << (ob.pickIndex()) << endl;
}
入力
[1, 3] で初期化し、pickIndex を5回呼び出します。
出力
1 1 1 1 0
この出力例では、重み 3 を持つインデックス 1 が多く選ばれていることが確認できます。ただし、ランダムな選択であるため、実行するたびに結果は異なる可能性がある点に注意してください。
計算量のまとめ
- コンストラクタ: O(n) — 累積和の計算に配列全体を一度走査します。
- pickIndex(): O(log n) — 二分探索(
upper_bound)による選択を行います。 - 空間計算量: O(n) — 累積和を格納する配列が必要です。
-
C++で学ぶ式ツリー(Expression Tree)の基本と具体例
式ツリーとは何か式ツリー(Expression Tree)とは、二分木の一種であり、木の各ノードが「演算子」または「オペランド(被演算子)」のいずれかで構成される特殊なデータ構造です。数式を木構造として表現することで、コンパイラや電卓アプリなどが数式を効率的に解析・評価できるようになります。ノードの役割式ツリーにおける各ノードは、次のように役割が分かれています。葉ノード(リーフノード):オペランド(数値や変数)を表します。非葉ノード(内部ノード):演算子(+、-、*、/ など)を表します。つまり、計算の対象となる値は必ず葉に配置され、それらをどのように処理するかを示す演算子が親ノードとして上に
-
C++で解く「3nスライスのピザ」問題 ― 動的計画法でスライスの合計を最大化する方法
問題の概要 大きさがまちまちの 3n 個のスライスからなるピザがあるとします。私と友人2人は、次のルールに従ってピザを取っていきます。 私が任意のスライスを1枚選びます。 友人のAmalは、私が選んだスライスの反時計回り方向に隣接するスライスを取ります。 友人のBimalは、私が選んだスライスの時計回り方向に隣接するスライスを取ります。 ピザのスライスがなくなるまで、この手順を繰り返します。 各スライスの大きさは、時計回りの順に並べた環状配列 slices として与えられます。求めるのは、私が手にできるスライスの大きさの合計の最大値です。 入出力例 入力が [9, 8, 6, 1, 1,