C++で解くワイン販売問題:動的計画法で最大利益と最適な売却順序を求める
問題の概要
n本のワインが一列に並んでおり、各ワインの価格を表す整数が与えられます。毎年、列の先頭または末尾にあるワインを1本売ることができます。ワインの価値は年月とともに上昇し、初年度の各ワインの利益を P1, P2, P3, …, Pn とすると、Y年目における i 番目のワインの利益は Y × Pi で表されます。毎年、先頭のワインを売る場合は「start」、末尾のワインを売る場合は「end」を出力し、最終的にすべてのワインから得られる最大利益を計算してください。
入力例
ワインの価格が {2, 4, 6, 2, 5} の場合、出力は以下のようになります:
start end end start start
最大利益 = 64アルゴリズム
この問題は動的計画法(DP)を用いて効率的に解くことができます。
- まだ売っていないワインの範囲を区間 [begin, end] として管理し、各区間ごとの最大利益を dp 配列に記録してメモ化することで、同じ状態の再計算を防ぎます。
- 現在の年数は year = n − (end − begin) で求められます。これは、これまでに売却済みのワインの本数に相当します。
- 「先頭のワインを売る場合」と「末尾のワインを売る場合」のそれぞれの利益を計算し、大きい方を採用します。同時に、どちらを選んだかを sell 配列に記録しておきます。
- 最後に sell 配列を先頭から順にたどることで、最適な売却順序(start / end)を復元できます。
状態数は O(n²)、各状態の計算は O(1) であるため、時間計算量・空間計算量はいずれも O(n²) となります。
実装例(C++)
#include <bits/stdc++.h>
using namespace std;
#define N 1000
int dp[N][N];
int sell[N][N];
int maxProfitUtil(int price[], int begin, int end, int n) {
if (dp[begin][end] != -1) {
return dp[begin][end];
}
int year = n - (end - begin);
if (begin == end) {
return year * price[begin];
}
int x = price[begin] * year + maxProfitUtil(price, begin + 1, end, n);
int y = price[end] * year + maxProfitUtil(price, begin, end - 1, n);
int ans = max(x, y);
dp[begin][end] = ans;
if (x >= y) {
sell[begin][end] = 0;
} else {
sell[begin][end] = 1;
}
return ans;
}
int maxProfit(int price[], int n) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
dp[i][j] = -1;
}
}
int ans = maxProfitUtil(price, 0, n - 1, n);
int i = 0, j = n - 1;
while (i <= j) {
if (sell[i][j] == 0) {
cout << "start ";
i++;
} else {
cout << "end ";
j--;
}
}
cout << endl;
return ans;
}
int main() {
int price[] = { 2, 4, 6, 2, 5 };
int n = sizeof(price) / sizeof(price[0]);
int ans = maxProfit(price, n);
cout << "Maximum profit = " << ans << endl;
return 0;
}
出力結果
上記のプログラムをコンパイルして実行すると、以下の出力が得られます。
start end end start start
Maximum profit = 64
-
C++で解くジョブスケジューリング問題:重複しないタスク選択による最大利益の求め方
問題の概要n個の異なるタスクがあるとします。各タスクiは startTime[i] から endTime[i] まで実行され、完了すると profit[i] の利益が得られます。startTime・endTime・profit の3つのリストが与えられたとき、実行時間帯が互いに重ならないようなタスクの部分集合の中で、得られる利益の合計が最大になる値を求めてください。なお、あるタスクが時刻Xに終了する場合、同じ時刻Xに開始する別のタスクを選ぶことは可能です(終了時刻と開始時刻が一致していても重複とはみなしません)。入力例startTime = [1,2,3,3]、endTime = [3,4,5
-
C++で木構造から辺を削除し、全ての連結成分が偶数頂点となる森を作る最大削除数を求める
問題概要 頂点数が偶数である無向木(ツリー)が与えられます。この木からできるだけ多くの辺を取り除き、削除後の森(フォレスト)を構成する各連結成分の頂点数がすべて偶数になるようにします。求めるのは、削除できる辺の最大本数です。 例 上図の木では、赤色で示した 0–2 と 0–4 の2本の辺を削除すると、各連結成分の頂点数がすべて偶数になります。これがこの木における削除可能な辺の最大本数です。 考えるポイント 木全体の頂点数が偶数であるため、サイズが奇数の部分木は必ず偶数個存在し、それらは最終的にどこかで結合して偶数サイズの成分を形成します。つまり、DFSで部分木のノード数を数え、そのサイズが