CS Academy BST Fixed Height

リンク : https://csacademy.com/contest/archive/task/bst-fixed-height/statement/

問題概要

Nこの頂点を持ち、深さHの完全二分木の一部を使った木は何通りあるか求めよ。

解説

正直自分にはこの解説はかけない。最初、dp[i][j][k]:深さiのときの頂点をj個をすでに使い、i-1の頂点がk個あるときと考えたが、到底間に合わない。そこで求めるべきdpは[i][j]でi個すでに使い、深さjのときの通り数である。そこで、dp[i][j]の通り数は右の部分木と左の部分木のどちらかがj-1であればよい。両方j-1のときと、片方がj-1でありもう片方はj-1未満であるときを求めればよい。
両方j-1のときは
dp[i][j]+=sum{k=0,1..i}{dp[k][j-1]×dp[i-k][j-1]}
片方j-1のときは
dp[i][j]+=sum{k=0,1...i}{dp[k][j-1]×sum{l=0,1...j-2}{dp[i-k][l]}}
ここで計算量を減らすために、sum{l=0,1...j-2}{dp[i-k][l]}の部分は別で値を持っておく必要がある。これでO(H*N2)で解けた。
しかし実は、このまま実装すると数ケースでTLEするので自明に必要のないforの範囲を縮めたりする必要がある。

LL n, h, dp1[600][600], dp2[600][600];
LL DP(int x, int y, int flag) {
    if (flag == 1) {
        if (y < 0)return 0;
        if (x < y)return 0;
        if (y == 1)return x == 1;
        if (dp1[x][y] != -1)return dp1[x][y];
        LL ret = 0;
        for (int i = y - 1; x - i - 1 >= y - 1; i++) {
            ret += DP(i, y - 1, 1)*DP(x - i - 1, y - 1, 1);
            ret %= MOD;
        }
        for (int i = y - 1; i < x; i++) {
            ret += DP(i, y - 1, 1)*DP(x - i - 1, y - 2, 2) * 2;
            ret %= MOD;
        }
        return dp1[x][y] = ret;
    }
    else {
        if (dp2[x][y] != -1)return dp2[x][y];
        if (x == 0)return 1;
        if (x < 0||y < 0)return 0;
        LL ret = 0;
        ret += DP(x, y, 1) + DP(x, y - 1, 2);
        ret %= MOD;
        return dp2[x][y] = ret;
    }
}
int main() {
    cin >> n >> h;
    rep(i, n + 1)rep(j, h + 1)dp1[i][j] = -1;
    rep(i, n + 1)rep(j, h + 1)dp2[i][j] = -1;
    cout << DP(n, h, 1) << endl;
    return 0;
}