面白かった! カタラン数的なものが登場する。
問題概要
制約
考えたこと
まずすぐにわかったことは、
- の中に、1 が 2 個以上あってはダメ
- の中に、1 が末尾以外の場所にあってはダメ
ということだった。そうすると、 は次のいずれかの形になる(ただし、先頭に 3 がない場合は 3 が 0 個とみなし、末尾が 1 の場合にはそれを除外したときの末尾に 2 がない場合は 2 が 0 個とみなす)。
- A:33...322...2 | 33...322...2 | ... | 33...322...2 | 1
- B:33...322...2 | 33...322...2 | ... | 33...322...2
ここで、各「33...322...2」をブロックと呼ぶことにしよう。少し手を動かすと、ブロックごとに独立に数えあげて、掛け算すればよいことがわかる。また、B の末尾の「33...322...2」だけ特殊であることもわかる。
具体的には、次のように考えられる。
- B の末尾のブロック以外のブロック:後方に 3 または 1 があるため、ブロック内の 2 は「1 + 1」によって作られることが確定する
- B の末尾のブロック:ブロック内の 2 のうち、前からいくつかは「1 + 1」によって作られ、残りは「2」によって作られる、という形をしている
それぞれ数える。
B の末尾のブロック以外のブロック
この場合は比較的容易だ。後半の 2 は必ず「1 + 1」によって作られる。たとえば、3 の個数が 2 個、2 の個数が 4 個の場合、次のように 5 通りとなる。
- 2, 2, 1, 1 | 1, 1, 1, 1, 1, 1, 1, 1
- 2, 1, 2, 1 | 1, 1, 1, 1, 1, 1, 1, 1
- 2, 1, 1, 2 | 1, 1, 1, 1, 1, 1, 1, 1
- 1, 2, 2, 1 | 1, 1, 1, 1, 1, 1, 1, 1
- 1, 2, 1, 2 | 1, 1, 1, 1, 1, 1, 1, 1
- 1, 1, 2, 2 | 1, 1, 1, 1, 1, 1, 1, 1:NG
つまり、2 個の 2 と 2 個の 1 を、1 が前に来すぎないように並べる問題となる。具体的には、3 の個数を 個、2 の個数を 個とすると、結局、次の問題になる。
個の 2 と 個の 1 を並べる方法のうち、先頭から何個とっても (1 の個数) - (2 の個数) が 1 以下であるような方法の個数
これは、カタラン数の要領で
通り
と求められる。
B の末尾のブロック
3 の個数を 個、2 の個数を 個とすると、まず 2 の部分を作るような並べ方が次の 通りが考えられる。
- (3 の部分) | 1 1, 1 1, ..., 1 1, 1 1, 1 1
- (3 の部分) | 1 1, 1 1, ..., 1 1, 1 1, 2
- (3 の部分) | 1 1, 1 1, ..., 1 1, 2, 2
- ...
- (3 の部分) | 1 1, 2, ..., 2, 2, 2
- (3 の部分) | 2, 2, ..., 2, 2, 2
このうち、上から 個の場合については、結局、上の場合と同様に
通り
と求められる。最後の「(3 の部分) | 2, 2, ..., 2, 2, 2」の場合については、1 をいくらでも後ろに送ることができるため、次の問題と同様になる。
個の 2 と 個の 1 を並べる方法のうち、先頭から何個とっても (1 の個数) - (2 の個数) が 1 以下であるような方法の個数
これは、カタラン数の要領で
通り
と求められる。
コード
以上の解法は の計算量で実装できる。
#include <bits/stdc++.h> using namespace std; // modint template<int MOD> struct Fp { // inner value long long val; // constructor constexpr Fp() : val(0) { } constexpr Fp(long long v) : val(v % MOD) { if (val < 0) val += MOD; } constexpr long long get() const { return val; } constexpr int get_mod() const { return MOD; } // arithmetic operators constexpr Fp operator + () const { return Fp(*this); } constexpr Fp operator - () const { return Fp(0) - Fp(*this); } constexpr Fp operator + (const Fp &r) const { return Fp(*this) += r; } constexpr Fp operator - (const Fp &r) const { return Fp(*this) -= r; } constexpr Fp operator * (const Fp &r) const { return Fp(*this) *= r; } constexpr Fp operator / (const Fp &r) const { return Fp(*this) /= r; } constexpr Fp& operator += (const Fp &r) { val += r.val; if (val >= MOD) val -= MOD; return *this; } constexpr Fp& operator -= (const Fp &r) { val -= r.val; if (val < 0) val += MOD; return *this; } constexpr Fp& operator *= (const Fp &r) { val = val * r.val % MOD; return *this; } constexpr Fp& operator /= (const Fp &r) { long long a = r.val, b = MOD, u = 1, v = 0; while (b) { long long t = a / b; a -= t * b, swap(a, b); u -= t * v, swap(u, v); } val = val * u % MOD; if (val < 0) val += MOD; return *this; } constexpr Fp pow(long long n) const { Fp res(1), mul(*this); while (n > 0) { if (n & 1) res *= mul; mul *= mul; n >>= 1; } return res; } constexpr Fp inv() const { Fp res(1), div(*this); return res / div; } // other operators constexpr bool operator == (const Fp &r) const { return this->val == r.val; } constexpr bool operator != (const Fp &r) const { return this->val != r.val; } constexpr Fp& operator ++ () { ++val; if (val >= MOD) val -= MOD; return *this; } constexpr Fp& operator -- () { if (val == 0) val += MOD; --val; return *this; } constexpr Fp operator ++ (int) const { Fp res = *this; ++*this; return res; } constexpr Fp operator -- (int) const { Fp res = *this; --*this; return res; } friend constexpr istream& operator >> (istream &is, Fp<MOD> &x) { is >> x.val; x.val %= MOD; if (x.val < 0) x.val += MOD; return is; } friend constexpr ostream& operator << (ostream &os, const Fp<MOD> &x) { return os << x.val; } friend constexpr Fp<MOD> pow(const Fp<MOD> &r, long long n) { return r.pow(n); } friend constexpr Fp<MOD> inv(const Fp<MOD> &r) { return r.inv(); } }; // Binomial coefficient template<class mint> struct BiCoef { vector<mint> fact_, inv_, finv_; constexpr BiCoef() {} constexpr BiCoef(int n) : fact_(n, 1), inv_(n, 1), finv_(n, 1) { init(n); } constexpr void init(int n) { fact_.assign(n, 1), inv_.assign(n, 1), finv_.assign(n, 1); int MOD = fact_[0].get_mod(); for(int i = 2; i < n; i++){ fact_[i] = fact_[i-1] * i; inv_[i] = -inv_[MOD%i] * (MOD/i); finv_[i] = finv_[i-1] * inv_[i]; } } constexpr mint com(int n, int k) const { if (n < k || n < 0 || k < 0) return 0; return fact_[n] * finv_[k] * finv_[n-k]; } constexpr mint fact(int n) const { if (n < 0) return 0; return fact_[n]; } constexpr mint inv(int n) const { if (n < 0) return 0; return inv_[n]; } constexpr mint finv(int n) const { if (n < 0) return 0; return finv_[n]; } }; const int MOD = 998244353; using mint = Fp<MOD>; int main() { int N, onenum = 0; cin >> N; BiCoef<mint> bc(N * 5); vector<int> S(N); for (int i = 0; i < N; i++) { cin >> S[i]; if (S[i] == 1) onenum++; } if (onenum > 1) { cout << 0 << endl; return 0; } if (onenum == 1 && S.back() != 1) { cout << 0 << endl; return 0; } // S をランレングス圧縮 using pint = pair<int, int>; deque<pint> ren; for (int i = 0; i < N; ) { int j = i; while (j < N && S[j] == S[i]) j++; if (S[i] != 1) ren.emplace_back(S[i], j - i); i = j; } if (ren[0].first != 3) ren.emplace_front(3, 0); if (ren.back().first != 2) ren.emplace_back(2, 0); // ブロックごとに考える mint res = 1; for (int i = 0; i+1 < ren.size(); i += 2) { int n = ren[i].second; int m = ren[i+1].second; mint one = bc.com(n*2, n) - bc.com(n*2, n-2); mint two = bc.com(n*2+m, n) - bc.com(n*2+m, n-2); if (S.back() == 1 || i+2 < ren.size()) res *= one; else res *= one * m + two; // パターン B の末尾のブロック } cout << res << endl; }