📜  落蛋拼图 | DP-11(1)

📅  最后修改于: 2023-12-03 15:27:52.302000             🧑  作者: Mango

落蛋拼图 | DP-11

落蛋拼图是一种基于动态规划算法的益智游戏。它在编程中有着广泛的应用,尤其是在需要优化时间和空间复杂度的场合。

算法流程
题目描述

落蛋拼图是一种由 $N \times M$ 的矩形网格和若干个蛋(可以理解成棋子)组成的益智游戏。游戏开始时,所有蛋都处于矩形网格的上方,玩家需要让这些蛋以某种方式落到矩形网格内,并且使得它们不会碰撞和重叠。具体地,对于每个蛋,我们可以选择它的落脚点(矩形网格中的一个格子),如果两个蛋在同一个格子上,则它们会碰撞;如果一个蛋的落脚点在另一个蛋上方,则它们会重叠。

解法分析

假设我们已经将前 $i-1$ 个蛋放到了网格中,现在需要考虑第 $i$ 个蛋应该放到哪个位置。不难发现,我们只需要将第 $i$ 个蛋从矩形网格的上方落脚到矩形网格的某个位置,然后就可以转化为一个规模更小的子问题:考虑前 $i-1$ 个蛋应该放到哪些位置,使得它们不会碰撞和重叠。我们可以用一个 $dp_{i,j}$ 数组表示前 $i$ 个蛋放在了第 $j$ 列时的最小代价,其中代价指的是所有蛋落地后形成的最小矩形面积。

考虑如何递推 $dp_{i,j}$。显然,第 $i$ 个蛋可以落到第 $j$ 列的任何位置,因此我们枚举其落脚点的行数 $k$,并计算在这种情况下的代价 $cost(i,j,k)$。其中,$cost(i,j,k)$ 是第 $i$ 个蛋落到第 $j$ 列的第 $k$ 行的代价,即以该点为右下角的最小矩形的面积。代价可以通过前缀和优化到 $O(1)$。

$dp_{i,j}$ 可以递推为:

$$ dp_{i,j} = \min_{k=1}^N { dp_{i-1,j} + cost(i,j,k) } $$

注意,这个转移方程只与 $dp_{i-1,j}$ 有关,因此我们可以使用滚动数组进行优化,将 $O(NM^2)$ 的时间复杂度降为 $O(NM)$。

最终的答案即为 $dp_{M,i}$,其中 $i$ 为 1 到 $N$ 的任意一个数。

代码实现

以下是使用 C++ 实现的代码片段。其中,$N,M,k$ 的取值范围分别为 $1 \le N,M \le 1000$,$1 \le k \le N$。

const int INF = 1e9;
int dp[2][1001], cost[1001][1001], sum[1001][1001];

int solve(int n, int m, const vector<int>& eggs) {
    if (n > m) return solve(m, n, eggs);

    for (int j = 1; j <= m; ++j) {
        dp[0][j] = INF; // 初始化为无穷大
        for (int k = 1; k <= n; ++k) {
            cost[j][k] = eggs[k-1] * (j-1) - sum[j][k-1]; // 计算代价
            sum[j][k] = sum[j][k-1] + cost[j][k]; // 求前缀和
        }
    }

    for (int i = 1; i <= n; ++i) {
        int curr = i % 2, prev = 1 - curr;
        dp[curr][0] = INF; // 边界
        for (int j = 1; j <= m; ++j) {
            dp[curr][j] = INF;
            for (int k = 1; k <= n; ++k) {
                dp[curr][j] = min(dp[curr][j], dp[prev][j] + cost[j][k]);
            }
        }
    }

    return dp[n%2][m];
}
结语

落蛋拼图是一种简单而又有趣的益智游戏,它不仅可以锻炼我们的思维能力,还可以训练我们编程中的动态规划技巧。我们可以通过不断优化算法的时间和空间复杂度,不断挑战更高难度的游戏,收获成就感的同时也提升自己的编程水平。