mayoko’s diary

プロコンとかいろいろ。

SRM 523 div1 med:BricksN

解法

典型 dp を 2 回やります。

まずひとつ目の dp は,
dp[l][i] = (i 番目までのブロックを使って長さ l のブロックを作る方法は何通りあるか)という dp です。
これは, まず l を i-1 番目以下のブロックを使う場合の dp[l][i-1] 通り, および 長さ i のブロックを使う場合を考えれば良いです。後者は, 初めて長さ i のブロックを使う場所がどこかによって場合分けをします。

もうひとつの dp は, dp[h][w] = (高さ h の時点で, 最大幅 w であるようなブロックの積み方は何通りあるか)という dp です。
これも似たような感じで解けます。長さが 1 以上のブロックを初めて置く場所がどこか, その長さはいくつか, の場合分けをして上手くメモ化再帰します。初めて置く場所は座標が大きい順にやると dfs がループすることがないので嬉しいです。

得た知見
  • 前の SRM で出てきた「初めて現れる場所」で場合分けする dp を使えた
  • dfs の順番気をつければループを防げる発想もなかなか出てこなかった
const int MAX = 55;
const ll MOD = 1e9+7;
ll dp[MAX][MAX];
ll how[MAX];
ll memo[MAX][MAX];
int H;

ll dfs(int h, int w) {
    if (h == H || w <= 0) return how[0];
    ll& ret = memo[h][w];
    if (ret >= 0) return ret;
    ret = how[0];
    for (int i = w-1; i >= 0; i--) {
        for (int l = 1; i+l <= w; l++) {
            int R = w-i-l-1;
            ll plus = dfs(h, R) * how[l] % MOD;
            (plus *= dfs(h+1, l)) %= MOD;
            (ret += plus) %= MOD;
        }
    }
    return ret;
}

class BricksN {
public:
    int countStructures(int w, int h, int k) {
        H = h;
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i <= k; i++) dp[0][i] = 1;
        for (int l = 1; l <= w; l++) {
            for (int i = 1; i <= k; i++) {
                dp[l][i] = dp[l][i-1];
                for (int s = 0; s+i <= l; s++) {
                    int L = s, R = l-s-i;
                    dp[l][i] += dp[L][i-1] * dp[R][i] % MOD;
                    dp[l][i] %= MOD;
                }
            }
        }
        for (int i = 0; i <= w; i++) {
            //cout << i << "  " << dp[i][k] << endl;
            how[i] = dp[i][k];
        }
        memset(memo, -1, sizeof(memo));
        return dfs(0, w);
    }