深入探究大楼丢鸡蛋问题

发布于

前言

之前写过一篇大楼丢鸡蛋问题,今天看到以前写的东西纯属胡扯,所以重写一下,因为今天有幸见到了大楼丢鸡蛋的终极强化版本:$10^3 \times 10^{18}$。

题目

URAL - 1597

有一个 $N$ 层的大楼,你有 $K$ 个鸡蛋。每个鸡蛋的硬度都是相同的。碎鸡蛋不能用来测试。你要求出 至少扔多少次才能保证测出鸡蛋的硬度。
假设你在第 $x$ 层扔下鸡蛋,如果鸡蛋碎了,说明鸡蛋硬度不能承受在 $x$ 层扔下。如果没碎则说明鸡蛋硬度能承受在 $x$ 层扔下。

$N, K \leq 10^{18}$,$10^5$ 组数据,$0.5$ 秒

题解

$\mathcal O\left(KN^2\right)$ 做法

记 $f_{i, j}$ 表示用 $i$ 个鸡蛋测 $j$ 层高的楼的最少扔的次数。
首先,我们可以枚举一个 $k$,表示这次鸡蛋在第 $k$ 层扔。
那么就会有两种情况:

  1. 鸡蛋碎了,鸡蛋硬度在 $k$ 层以下,并且损失一个鸡蛋。所以 $f_{i, j} = 1 + f_{i - 1, k - 1}$。
  2. 鸡蛋没碎,鸡蛋硬度在 $k$ 层以上,并且没有损失鸡蛋。所以 $f_{i, j} = 1 + f_{i, j - k}$。

由于是求最坏情况,所以要取两者的最大值。而又由于我们可以决定在哪一层扔鸡蛋,所以我们在所有的 $k$ 中取最小值。
于是式子就是这样:

$$f_{i, j} = 1 + \min\limits_{k = 1}^j\max\left\{f_{i - 1, k - 1}, f_{i, j - k}\right\}$$

signed STRUGGLING([[maybe_unused]] unsigned long TEST_NUMBER) {
  tp k, n; bin >> k >> n;
  if (k == 0 && n == 0) return 1;
  vector f(k + 1, vector<tp>(n + 1));
  for (tp i = 1; i <= n; ++i) f[1][i] = i;
  for (tp i = 2; i <= k; ++i) {
    for (tp j = 1; j <= n; ++j) {
      f[i][j] = INF;
      for (tp k = 1; k <= j; ++k) f[i][j] = min(f[i][j], max(f[i - 1][k - 1], f[i][j - k]) + 1);
    }
  }
  bin << f[k][n] << '\n';
  return 0;
}

一个小优化:$\mathcal O\left(KN \log N\right)$

不是主线内容,可以跳过。

其实,随着 $k$ 的增加,$f_{i - 1, k - 1}$ 单调不减,$f_{i, j - k}$ 单调不增。所以 $\max\left\{f_{i - 1, k - 1}, f_{i, j - k}\right\}$ 的最小值在两函数交点处。所以我们可以二分求出这个交点。

signed STRUGGLING([[maybe_unused]] unsigned long TEST_NUMBER) {
  tp k, n; bin >> k >> n;
  if (k == 0 && n == 0) return 1;
  vector f(k + 1, vector<tp>(n + 1));
  for (tp i = 1; i <= n; ++i) f[1][i] = i;
  for (tp i = 2; i <= k; ++i) {
    for (tp j = 2; j <= n; ++j) {
      tp l = 1, r = j;
      while (l <= r) {
        tp mid = l + r >> 1, a = f[i - 1][mid - 1], b = f[i][j - mid];
        if (a > b) r = mid - 1;
        else l = mid + 1;
      }
      f[i][j] = min(f[i][j], max(f[i - 1][r - 1], f[i][j - r]) + 1);
    }
  }
}

$\mathcal O\left(K \cdot Ans\right)$ 做法

现在做一个常用优化:设 $g_{i, j}$ 表示用 $i$ 个鸡蛋测 $j$ 次能确定的最高楼层。

于是我们有

$$g_{i, j} = g_{i - 1, j - 1} + 1 + g_{i, j - 1}$$

这个加 $1$ 是因为可以确定当前丢鸡蛋的这一层。

我们分析一下此时的复杂度:

状态数是 $K \cdot Ans$ 的,转移是常数的,因此总复杂度为 $\mathcal O\left(K \cdot Ans\right)$

我这里偷了点懒,写了个 $\mathcal O\left(KN\right)$。

signed STRUGGLING([[maybe_unused]] unsigned long TEST_NUMBER) {
  tp k = 1000, n = 1000;
  vector g(k + 1, vector<tp>(n + 1));
  for (tp i = 1; i <= n; ++i) g[1][i] = i;
  for (tp i = 1; i <= n; ++i) {
    for (tp j = 2; j <= k; ++j) {
      g[j][i] = g[j - 1][i - 1] + 1 + g[j][i - 1];
      if (g[j][i] >= n) {
        while (j <= k) g[j++][i] = INF;
        break;
      }
    }
  }
  [&]() -> void {
    tp l = 1, r = n, tar = -1;
    while (l <= r) {
      tp mid = (l + r) / 2;
      if (g[k][mid] >= n) { r = mid - 1; tar = mid; }
      else l = mid + 1;
    }
    bin << tar << '\n';
  }();
  return 0;
}

能过 $10^{18}$ 做法

此时要做我们的点睛一笔。我们发现 $g_{i, j}$ 的递推关系很像组合数的递推关系。

只需令 $h_{i, j} = g_{i, j} + 1$,此时 $h_{i, j} = h_{i - 1, j - 1} + h_{i, j - 1}$

这样就是组合数了。

但是我们并不需要这样做(

我们发现,当 $K \geq \log_2N$ 时,我们可以直接二分,这样是最优的(理性理解是去掉限制跑 DP)。

而 $K = 1, 2, 3$ 时,我们可以手动推公式算。

而 $K \geq 4$ 时,答案不超过 $7000$,因此,我们只需要计算 $64 \times 7000 = 448000$ 个 DP 值即可。

URAL - 1597:

// :/

tp log2(tp x) {
  tp l = 1, r = x, ret = 0;
  while(l<=r){
    tp mid = (l+r) / 2;
    ++ret;
    if (mid - l > r - mid) r = mid - 1;
    else l = mid + 1;
  }
  return ret;
}

signed STRUGGLING([[maybe_unused]] unsigned long TEST_NUMBER) {
  tp k = 64, n = 1e5;
  vector g(k + 1, vector<tp>(n + 1));
  for (tp i = 1; i <= n; ++i) g[1][i] = i;
  for (tp i = 1; i <= n; ++i) {
    for (tp j = 2; j <= k; ++j) {
      g[j][i] = g[j - 1][i - 1] + 1 + g[j][i - 1];
      if (g[j][i] >= INF) {
        while (j <= k) g[j++][i] = INF;
        break;
      }
    }
  }
  while (static_cast<void>(bin >> k >> n), k != 0 || n != 0) {
    if (k == 1) { bin << n << '\n'; continue; }
    if (k == 2) {
      tp l = 1, r = 2e9, tar = -1;
      while (l <= r) {
        tp mid = (l + r) / 2;
        if (mid * mid + mid >= 2 * n) { r = mid - 1; tar = mid; }
        else l = mid + 1;
      }
      bin << tar << '\n';
      continue;
    }
    if (k == 3) {
      tp l = 1, r = 2e6, tar = -1;
      while (l <= r) {
        tp mid = (l + r) / 2;
        if (mid * mid * mid + 5 * mid >= 6 * n) { r = mid - 1; tar = mid; }
        else l = mid + 1;
      }
      bin << tar << '\n';
      continue;
    }
    if (k >= log2(n)) { bin << log2(n) << '\n'; continue; }
    tp l = 1, r = 1e5, tar = -1;
    while (l <= r) {
      tp mid = (l + r) / 2;
      if (g[k][mid] >= n) { r = mid - 1; tar = mid; }
      else l = mid + 1;
    }
    bin << tar << '\n';
  }
  return 0;
}

void MIST() {
}

// :\ */

暂无评论

发表评论