C++で解くIPO問題:最大k件のプロジェクト選択で資本を最大化する方法
問題の概要
ある会社AがまもなくIPO(新規株式公開)を控えているとしましょう。自社の株式を良い価格で売却するために、A社はIPO前にいくつかのプロジェクトへ取り組み、資本を増やしたいと考えています。しかし、A社のリソースには限りがあり、IPOまでに完了できるのは最大k件の異なるプロジェクトだけです。最大k件のプロジェクトを完了した後の総資本を最大化する最適な戦略を設計して、A社を助けましょう。
各プロジェクトiには純利益 Pi と、着手に必要な最小資本 Ci が設定されています。初期資本は W です。プロジェクトを完了すると純利益を獲得でき、その利益は総資本に加算されます。
つまりこの問題は、与えられたプロジェクトの中から最大k件の異なるプロジェクトを選び、最終的な資本を最大化することです。答えとして、最大化された最終資本を出力します。
具体例
入力が以下の場合を考えてみます。
- k = 2(実行できるプロジェクト数)
- W = 0(初期資本)
- Profits = [1, 2, 4](各プロジェクトの純利益)
- Capital = [0, 1, 1](各プロジェクトの必要資本)
このとき出力は 5 になります。手順を見てみましょう。
- 初期資本が0のため、着手できるのはインデックス0のプロジェクトのみ。これを実行して利益1を得て、資本は1になります。
- 資本1があれば、インデックス1または2のプロジェクトに着手できます。より多くの利益を得るため、利益4のインデックス2を選択します。
したがって最終的な答えは 0 + 1 + 4 = 5 となります。
解法のアプローチ
この問題は、貪欲法と優先度付きキュー(ヒープ)を組み合わせることで効率的に解けます。考え方はシンプルで、「現在の資本で着手可能なプロジェクトの中から、純利益が最大のものを選ぶ」という操作をk回繰り返すだけです。
具体的な手順は以下の通りです。
- 優先度付きキュー pqCapital と pqMain を作成する
- n := Profits のサイズとする
- i := 0 から初期化し、i < n の間、i を1ずつ増やしながら実行する
- { Profits[i], Capital[i] } を pqCapital に挿入する
- i := 0 から初期化し、i < k の間、i を1ずつ増やしながら実行する
- pqCapital が空でなく、先頭要素の資本値が W 以下である間、次を繰り返す
- pqCapital の先頭要素を pqMain に移動する
- pqCapital からその要素を削除する
- pqMain が空の場合はループを抜ける(これ以上着手できるプロジェクトがない)
- W := W + pqMain の先頭要素の利益値とする
- pqMain から先頭要素を削除する
- pqCapital が空でなく、先頭要素の資本値が W 以下である間、次を繰り返す
- W を返す
ここで重要なポイントは、pqCapital を「必要資本の昇順」に、pqMain を「純利益の降順」に並べることです。こうすることで、現在の資本 W で着手できるすべてのプロジェクトを pqMain へ移しておき、その中から最も利益の大きいものを即座に取り出せるようになります。
C++での実装例
それでは、上記のアルゴリズムをC++で実装してみましょう。
サンプルコード
#include <bits/stdc++.h>
using namespace std;
struct Comparator{
bool operator() (pair <int, int> a, pair <int, int> b){
return !(a.second < b.second);
}
};
class Solution {
public:
int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) {
priority_queue < pair <int, int>, vector < pair <int, int> >, Comparator> pqCapital;
priority_queue < pair <int ,int> > pqMain;
int n = Profits.size();
for(int i = 0; i < n; i++){
pqCapital.push({Profits[i], Capital[i]});
}
for(int i = 0; i < k; i++){
while(!pqCapital.empty() && pqCapital.top().second <= W){
pqMain.push(pqCapital.top());
pqCapital.pop();
}
if(pqMain.empty()) break;
W += pqMain.top().first;
pqMain.pop();
}
return W;
}
};
main(){
Solution ob;
vector<int> v = {1,2,4}, v1 = {0,1,1};
cout << (ob.findMaximizedCapital(2,0, v, v1));
}入力
2 0 [1,2,4] [0,1,1]
出力
5
計算量について
時間計算量は O(n log n) オーダーで評価できます。すべてのプロジェクトを pqCapital へ挿入するのに O(n log n) かかり、着手可能なプロジェクトを pqMain へ移す処理では各プロジェクトが高々1回しか移動しないためです。さらに、k回の選択それぞれに対して O(log n) のコストが発生します。空間計算量は、すべてのプロジェクトを保持するために O(n) となります。
-
C++で解くリスのナッツ収集シミュレーション ― 最小移動距離を求めるアルゴリズム
問題概要 1本の木、1匹のリス、そして複数のナッツがフィールド上にあります。それぞれの位置は2次元グリッドのセルで表現されます。この問題の目的は、リスがすべてのナッツを集めて木の下に1個ずつ運ぶときの最小移動距離を求めることです。 リスの行動には次の制約があります。 一度に持てるナッツは最大1個 移動は上下左右の4方向で、隣接するセルへのみ可能 距離は移動回数(ステップ数)で表される たとえば、入力が「高さ: 5 / 幅: 7 / 木の位置: [2,2] / リスの位置: [4,4] / ナッツ: [[3,0], [2,5]]」の場合、出力は 12 となります。 解法のポイント まず、
-
C++で解く長方形エリアII ― 座標圧縮と走査線法による被覆面積の計算
問題概要 軸に平行な長方形のリストが与えられるものとします。各 rectangle[i] = {x1, y1, x2, y2} において、(x1, y1) は i 番目の長方形の左下隅の座標、(x2, y2) は右上隅の座標を表します。 求めたいのは、平面上でこれらすべての長方形が覆っている領域の合計面積です。答えは非常に大きな値になる可能性があるため、109 + 7 で割った余りを返すことになっています。 たとえば、入力が次のような場合を考えてみましょう。 このとき、出力は 6 となります。 解法の方針:座標圧縮+走査線(スイープライン) この問題は、座標圧縮(座標の離散化)と走査線法(