一、题意

有n(n<=30)种立方体,每种有无穷个。现在要求你选一些立方体摞成尽量高的一个柱子,上面的立方体底面长宽必须严格小于下面的立方体。问最大高度。

二、解析

由于上面的立方体底面长宽必须严格小于下面的立方体,因此不会出现环,也就是说这是一个典型的DAG(有向无环图)问题。

有向无环图可以通过记忆化搜索(动态规划)的方式来解决,因此直接套用模板即可。
唯一要注意的是这道题里的状态需要用两个参数来表示,即i, k。i表示这是第几种立方体,k表示这个立方体哪条边作为高。因此记忆化搜索是状态的存放数据memo也应该是两维的。

三、代码

#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 30 + 5;
int kase = 1, n, a[maxn][3], memo[maxn][3];

bool isFit(int v, int vk, int u, int uk) {
    int va = a[v][(vk + 1) % 3], vb = a[v][(vk + 2) % 3];
    int ua = a[u][(uk + 1) % 3], ub = a[u][(uk + 2) % 3];
    return va < ua && vb < ub || va < ub && vb < ua;
}

int dp(int u, int uk) {
    int & res = memo[u][uk];  // 记忆化搜索
    if(res != -1) return res;

    res = a[u][uk];
    for(int v = 0; v < n; v ++) for(int vk = 0; vk < 3; vk ++) {
        if(isFit(v, vk, u, uk)) res = max(res, a[u][uk] + dp(v, vk));
    }
    return res;
}

int main() {
    while(cin >> n && n) {
        for(int i = 0; i < n; i ++) cin >> a[i][0] >> a[i][1] >> a[i][2];
        memset(memo, -1, sizeof(memo));
        int ans = 0;
        for(int i = 0; i < n; i ++) for(int k = 0; k < 3; k ++) ans = max(ans, dp(i, k));
        cout << "Case " << kase ++ << ": maximum height = " << ans << endl;
    }
}

四、归纳

  • 当问题中出现单向依赖关系时,可以考虑转化为DAG问题进行处理
  • DAG问题的最长路问题可以通过记忆化搜索来解决,在DAG最长路问题中,dp(i)表示以i为起点的最长路
    模板大致为:
int dp(int u) {
    int & res = memo[u];  // 记忆化搜索
    if(res != -1) return res;

    res = ...;
    for(int v = 0; v < n; v ++) if([依赖条件]) {
        res = max(res, dp(v, vk) + ....);
    }
    return res;
}
  • 通过引用的方式引用memo,就不容易忘记记录结果到memo中
  • 记忆化搜索的时间复杂度 = 状态数 * 每个状态的决策数量。在本题中就是 3n * 3n = O(n^2)