E - Count 123 解説 by MoSooN

畳み込みを使用した解法

E - Count 123 畳み込み解法

著者コメント

想定に対して高度なライブラリを使用しますが、実際の考察の負荷は低いと思います。

※ 文章中の \((\ \ )\) は組合せ(二項係数)を表し、

\[ \binom{n}{k} = \frac{n!}{k!(n-k)!} \]

を意味します。

立式

まず 2 を先に並べる。

このとき、2 の前後にある隙間は全部で X_2+1 個ある。

_ 2 _ 2 _ ... _ 2 _

13 は隣り合えないので、同じ隙間に 13 の両方を入れることはできない。

そこで、使う隙間の個数を S とする。

さらに、その S 個の隙間のうち、1 を入れる隙間の個数を P とする。

すると、3 を入れる隙間の個数は S-P 個である。

固定した S, P に対する寄与を考える。

まず、X_2+1 個の隙間から使う S 個を選ぶ方法は

\[ \binom{X_2+1}{S} \]

次に、その S 個のうち P 個を 1 用にする方法は

\[ \binom{S}{P} \]

通りである。

残りの S-P 個の隙間は 3 用になる。

X_1 個の 1P 個の非空な塊に分ける方法は

\[ \binom{X_1-1}{P-1} \]

通りである。

同様に、X_3 個の 3S-P 個の非空な塊に分ける方法は

\[ \binom{X_3-1}{S-P-1} \]

通りである。

よって、固定した S, P に対する寄与は

\[ \binom{X_2+1}{S} \binom{S}{P} \binom{X_1-1}{P-1} \binom{X_3-1}{S-P-1} \]

である。

したがって、答えは

\[ \sum_S \sum_P \binom{X_2+1}{S} \binom{S}{P} \binom{X_1-1}{P-1} \binom{X_3-1}{S-P-1} \]

である。


畳み込みの形にする

Q = S-P とおく。

このとき、

\[ \binom{S}{P}= \frac{S!}{P!Q!} \]

である。

したがって、固定した S, P に対する寄与は

\[ \binom{X_2+1}{S} S! \left( \frac{\binom{X_1-1}{P-1}}{P!} \right) \left( \frac{\binom{X_3-1}{Q-1}}{Q!} \right) \]

と変形できる。

ここで

\[ A_P = \frac{\binom{X_1-1}{P-1}}{P!} \]

\[ B_Q = \frac{\binom{X_3-1}{Q-1}}{Q!} \]

とおく。

S を固定したとき、P+Q=S となるすべての組を足せばよいので、

\[ C_S = \sum_{P+Q=S} A_PB_Q \]

と書ける。

これは AB の畳み込みそのものである。

よって C = convolution(A, B) とすればよい。

最終的な答えは

\[ \sum_S \binom{X_2+1}{S} S! C_S \]

である。


計算量

階乗と階乗逆元を前計算しておけば、二項係数は O(1) で求められる。

畳み込みを NTT で行うので、全体の計算量は

\[ O((X_1+X_3)\log(X_1+X_3)) \]

である。


実装例 C++

コードは解説用に改めて書いたものです。

#include <bits/stdc++.h>
#include <atcoder/convolution>
using namespace std;
using namespace atcoder;

using mint = modint998244353;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int X1, X2, X3;
    cin >> X1 >> X2 >> X3;

    int N = max({X1, X2 + 1, X3}) + 5;

    vector<mint> fact(N), ifact(N);

    fact[0] = 1;

    for (int i = 1; i < N; i++) {
        fact[i] = fact[i - 1] * i;
    }

    ifact[N - 1] = fact[N - 1].inv();

    for (int i = N - 1; i >= 1; i--) {
        ifact[i - 1] = ifact[i] * i;
    }

    auto C = [&](int n, int k) -> mint {
        if (k < 0 || k > n) return 0;
        return fact[n] * ifact[k] * ifact[n - k];
    };

    vector<mint> A(X1 + 1), B(X3 + 1);

    for (int p = 1; p <= X1; p++) {
        A[p] = C(X1 - 1, p - 1) * ifact[p];
    }

    for (int q = 1; q <= X3; q++) {
        B[q] = C(X3 - 1, q - 1) * ifact[q];
    }

    vector<mint> conv = convolution(A, B);

    mint ans = 0;

    for (int s = 0; s <= X2 + 1 && s < (int)conv.size(); s++) {
        ans += C(X2 + 1, s) * fact[s] * conv[s];
    }

    cout << ans.val() << '\n';
}

投稿日時:
最終更新: