mayoko’s diary

プロコンとかいろいろ。

yukicoder No.93 ペガサス

問題名, 「ひま」でも良かったと思います。

解法

writer の解説がわかりやすいのであんまり言うことがないです。
ペガサス - 解説

ただ dp の遷移が結構難しいというか面倒です。

ソースコードでコメントの説明をつけましたが少しだけ補足します。

解説ページで説明しているように dp を作っていますが, for 文で回すのは n と 条件を満たさない隣接関係の総数 のみにして, 他のフラグはすべて for 文の中で場合分けしています。
others とコメントがついてるところは, ng な並び方をしていないペアの間に n+1 を入れる場合(例えば {1,5})ですが, これは ng なペアの数及び n-1 の隣以外の場所なので, 大抵の場合は (n+1 個) - (ng+2 個) だけあります。たまに 引く数が (ng+1) 個の場合がありますがこれは n-1 の隣になるものが n-1 と n-3 で ng 回避するものと 単純に n-1 のとなりにするもので分かれているからです。

得た知見
  • 難しい動的計画法では, 「n 個目の要素をどのように今の状態に組み込んでいくか」が大事
    • 今回は n*n の並びから (n+1)*(n+1) の並びを作るために n+1 個目の要素は n 個の要素の間のどこかに入れていく感じだった
  • 状態をなるべく簡素に保つ
    • 難しい dp の要はここですね。これだけ言っても何もって感じですが
const int MAXN = 1003;
const ll MOD = 1e9+7;
ll dp[MAXN][MAXN][2][2]; // n, ng, (n-3,n-1), (n-2, n) -> num

inline void add(ll& a, ll b) {
    (a += b) %= MOD;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    if (N <= 2) {
        cout << N << endl;
        return 0;
    }
    dp[2][0][0][0] = 2;
    for (int n = 1; n < N; n++) for (int ng = 0; ng < n; ng++) {
        // (n-3, n-1) (n-2, n)
        ll v = dp[n][ng][1][1];
        if (v) {
            // split n-2, n
            add(dp[n+1][ng-1][0][0], v);
            // split n-3, n-1
            add(dp[n+1][ng][1][1], v);
            // adjacent to n-1
            add(dp[n+1][ng+1][1][1], v);
            // split others
            add(dp[n+1][ng-1][1][0], v*(ng-2));
            // others
            add(dp[n+1][ng][1][0], v*((n+1)-(ng+1)));
        }
        // (n-3, n-1)
        v = dp[n][ng][1][0];
        if (v) {
            // split n-3, n-1
            add(dp[n+1][ng][0][1], v);
            // adjacent to n-1
            add(dp[n+1][ng+1][0][1], v);
            // split others
            add(dp[n+1][ng-1][0][0], v*(ng-1));
            // others
            add(dp[n+1][ng][0][0], v*((n+1)-(ng+1)));
        }
        // (n-2, n)
        v = dp[n][ng][0][1];
        if (v) {
            // split n-2, n
            add(dp[n+1][ng-1][0][0], v);
            // adjacent to n-1
            add(dp[n+1][ng+1][1][1], 2*v);
            // split others
            add(dp[n+1][ng-1][1][0], v*(ng-1));
            // others
            add(dp[n+1][ng][1][0], v*((n+1)-(ng+2)));
        }
        // none
        v = dp[n][ng][0][0];
        if (v) {
            // adjacent to n-1
            add(dp[n+1][ng+1][0][1], 2*v);
            // split
            if (ng > 0) add(dp[n+1][ng-1][0][0], v*ng);
            // others
            add(dp[n+1][ng][0][0], v*((n+1)-(ng+2)));
        }
    }
    cout << dp[N][0][0][0] << endl;
    return 0;
}