[AGC064D] Red and Blue Chips 題解

下蛋爷發表於2024-10-09

Description

你有 \(N\) 個字串,初始情況下每個字串只有一個字元,是 \(\texttt{R}\)\(\texttt{B}\),保證第 \(N\) 個字串是 \(\texttt{B}\)

你需要對每個 \(i=1,2,\cdots ,n-1\) 執行以下操作:

  • 選擇一個整數 \(j\) 使得 \(i< j\le n\),且第 \(j\) 個字串的最後一個字元是 \(\texttt{B}\),然後把第 \(i\) 個字串整體拼接在第 \(j\) 個字串的前面

問最後可以得到多少種本質不同的第 \(N\) 個字串,對 \(998244353\) 取模。

\(2\leq N\leq 300,s_N=\texttt{B}\)

Solution

考慮怎麼判斷一個終止串 \(t\) 是否合法。

容易發現終止串形如一棵樹的後序遍歷的形式,所以可以倒著為每個位置找父親。

在找父親的過程中把終止串掛在最後一個 B 上,設 \(m\) 為原串 B 的個數,\(f_i\) 表示掛在 \(i\) 上的串。

設當前走到了 \(i\),如果當前 \(s_i\)R,就選擇一個 \(f_j\) 滿足 \(f_j\) 開頭為 R 並把開頭的 R 刪掉,表示 \(i\) 的父親為 \(j\)。否則就需要把某個 \(f_j\) 在字元為 B 的位置分裂,並且把分裂後前面的串掛在 \(i\) 上。

由於我們需要儘量讓當前所有 \(f_i\) 開頭的 R 數量和最多,所以每次選擇長度最大的極長 R 段前分裂一定最優。容易發現這麼做如果存在一個時刻滿足 \(s_i\)R 且所有 \(f_i\) 的開頭都不為 R,那麼這個串一定不合法。

形式化的描述就是設 \(a_i\) 表示初始串倒數第 \(i\) 個極長 R 連續段的長度,\(b_i\) 表示終止串倒數第 \(i\) 個極長 R 連續段的長度。

又因為 \(b_1\) 顯然不能動,所以只能對 \(b_2,b_3,\ldots,b_m\) 排序,排序後合法的充分必要條件為 \(\forall j\in[1,m],\sum_{i=1}^{j}{a_i}\leq\sum_{i=1}^{j}{b_i}\)

方案數容易 dp 得到。

時間複雜度:\(O(n^3\ln n)\)

Code

#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 305, kMod = 998244353;

int n, m;
int a[kMaxN], sum[kMaxN], f[kMaxN][kMaxN], fac[kMaxN], ifac[kMaxN];
std::string str;

constexpr int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void prework(int n = 300) {
  fac[0] = ifac[0] = 1;
  for (int i = 1; i <= n; ++i) {
    fac[i] = 1ll * i * fac[i - 1] % kMod;
    ifac[i] = qpow(fac[i]);
  }
}

void dickdreamer() {
  std::cin >> n >> str;
  int lst = 0;
  for (int i = 1; i <= n; ++i) {
    if (str[i - 1] == 'B') {
      a[++m] = i - lst - 1;
      lst = i;
    }
  }
  prework();
  std::reverse(a + 1, a + 1 + m);
  for (int i = 1; i <= m; ++i) sum[i] = sum[i - 1] + a[i];
  int cnt = n - m;
  for (int i = a[1]; i <= cnt; ++i) f[1][i] = fac[m - 1];
  for (int i = cnt; ~i; --i) {
    for (int j = m; j; --j) {
      for (int k = cnt; k >= sum[j]; --k) {
        for (int c = 1; c <= std::min(j, (i ? k / i : n)); ++c) {
          if (k - i * (c - 1) >= sum[j - c + 1]) inc(f[j][k], 1ll * f[j - c][k - i * c] * ifac[c] % kMod);
          else break;
        }
      }
    }
  }
  std::cout << f[m][cnt] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

相關文章