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

C++で解くスーパーエッグドロップ問題 ― 最小手数を求めるアルゴリズム

問題概要

K個の卵と、1階からN階まである建物が与えられます。すべての卵は同じ性能を持ち、一度割れた卵は二度と使用できません。

0以上N以下のどこかに臨界階Fが存在し、Fより高い階から卵を落とすと必ず割れ、F以下の階から落とした場合は割れないものとします。各手番では、卵を1つ選び、1〜Nの範囲内の任意の階Xから落とすことができます。

目的は、Fの値を確実に特定することです。Fの初期値が何であっても対応できるようにするには、最低何回の操作が必要でしょうか。

例えば、入力が K = 2、N = 6 の場合、出力は 3 となります。

解法の考え方

この問題は、動的計画法(DP)と二分探索を組み合わせることで効率的に解けます。ある階midから卵を落としたとき、結果は次の2通りです。

  • 卵が割れた場合: 残り卵はK−1個となり、探索範囲はmid−1階以下に絞られます。
  • 卵が割れなかった場合: 卵はK個のまま残り、探索範囲はN−mid階に絞られます。

最悪ケースの手数を最小化したいので、各階について「割れた場合」と「割れなかった場合」のうち大きい方(最悪値)を取り、その中で最小となる階を二分探索で探します。さらに、同一状態の再計算を避けるためメモ化で計算結果をキャッシュします。

アルゴリズムの手順

  1. 2次元配列dpを定義する。
  2. solve(K, N) 関数を定義する。
  3. N ≤ 1 の場合は N を返す。
  4. K = 1 の場合は N を返す(卵が1個しかない場合は下の階から順に試すしかないため)。
  5. dp[K][N] ≠ −1 の場合は dp[K][N] を返す(メモ化済み)。
  6. ret := N、low := 0、high := N と初期化する。
  7. low ≤ high の間、以下を繰り返す。
    • mid := low + (high − low) / 2
    • left := 1 + solve(K − 1, mid − 1)
    • right := 1 + solve(K, N − mid)
    • ret := min(ret, max(left, right))
    • left = right ならループを抜ける。
    • left < right なら low := mid + 1、そうでなければ high := mid − 1。
  8. dp[K][N] = ret を返す。

main関数での処理

  • dp を (K + 1) × (N + 1) の2次元配列として作成し、すべて −1 で初期化する。
  • solve(K, N) の結果を返す。

以下の実装例を見ると、理解が深まるでしょう。

実装例

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    vector<vector<int>> dp;
    int solve(int K, int N) {
       if (N <= 1)
          return N;
       if (K == 1)
          return N;
       if (dp[K][N] != -1)
          return dp[K][N];
       int ret = N;
       int low = 0;
       int high = N;
       while (low <= high) {
          int mid = low + (high - low) / 2;
          int left = 1 + solve(K - 1, mid - 1);
          int right = 1 + solve(K, N - mid);
          ret = min(ret, max(left, right));
          if (left == right)
          break;
          if (left < right) {
             low = mid + 1;
          } else
             high = mid - 1;
       }
       return dp[K][N] = ret;
    }
    int superEggDrop(int K, int N) {
       dp = vector<vector<int>>(K + 1, vector<int>(N + 1, -1));
       return solve(K, N);
    }
};
main(){
    Solution ob;
    cout << (ob.superEggDrop(2,6));
}

入力

2, 6

出力

3

計算量

  • 時間計算量: O(K × N × log N) ― 状態の数は K × N、各状態で二分探索に log N 回の再帰呼び出しが発生します。
  • 空間計算量: O(K × N) ― メモ化テーブルdpのための領域が必要です。
  1. C++のenum(列挙型)とは?基本の使い方を徹底解説

    C++のenum(列挙型)とは?基本の使い方を徹底解説 列挙型(enumerated type)は、あらかじめ定義された値の範囲の中から、いずれか一つの値だけを持つことができるユーザー定義型です。 プログラミングでは、変数が特定の値の集合の中の一つの値しか格納できないようにしたい場合によく使われます。たとえば、変数に「曜日」だけを格納したい場合などに、列挙型が役立ちます。 この記事では、C++における列挙型の基礎知識、定義方法、コードでの実際の使い方を、サンプルコードとともにわかりやすく解説します。読み終える頃には、C++のenumを使いこなせるようになっているはずです。 C++におけ

  2. グラフ内のスーパー頂点を見つけるC++プログラムの解説

    問題の概要n個の頂点を持つグラフが与えられていると仮定しましょう。頂点には1からnまでの番号が付けられており、配列「edges」に含まれる辺によって互いに接続されています。さらに、各頂点は1からnの範囲の数値である「x」という値を持ち、その値は配列「values」で与えられます。このとき、グラフの中から「スーパー頂点(super vertex)」と呼ばれる特別な頂点を見つけ出す必要があります。頂点iがスーパー頂点であるとは、頂点1から頂点iへの最短経路上に、i番目の頂点と同じ「x」の値を持つ頂点が存在しないことを意味します。この条件を満たすすべての頂点を出力してください。たとえば、入力が n